C/C++内存管理

发布于:2024-12-19 ⋅ 阅读:(14) ⋅ 点赞:(0)

1. C/C++内存分布

在C和C++中,程序的内存分布通常包括以下几个部分:

1.代码区(Text Segment)

  1. 存放程序的机器指令,即程序的可执行代码。
  2. 这是只读区域,防止程序由于错误而修改自身的指令。

2.全局初始化数据区(Data Segment)

  1. 存放程序中已经初始化的全局变量和静态变量。
  2. 这些变量在程序启动时分配,并在程序结束时释放。

3.全局未初始化数据区(BSS Segment)

  1. 存放程序中未初始化的全局变量和静态变量。
  2. 这个区域在程序启动时会被初始化为0或空,并在程序结束时释放。

4.栈区(Stack)

  1. 用于存放函数调用时的局部变量(包括函数参数、返回地址等)。
  2. 栈是后进先出(LIFO)的数据结构,由系统自动管理。
  3. 栈的大小通常是有限的,如果栈空间不足,可能会发生栈溢出。

5.堆区(Heap)

  1. 动态内存分配的区域,用于存放程序运行时动态分配的内存。
  2. 堆的大小不是固定的,可以根据需要进行扩展(直到系统资源耗尽)。
  3. 堆内存的管理需要程序员手动进行,使用mallocfree(C语言)或newdelete(C++语言)等函数。

6.常量区

  1. 存放程序中定义的常量,如字符串常量等。
  2. 这部分内存通常也是只读的。

7.程序堆(Heap)(与堆区不同)

  1. 在某些操作系统中,程序堆是堆区的另一种说法,但在其他情况下,它可能指的是操作系统为程序分配的内存空间,用于存储动态分配的数据。

8.内存映射区域

  1. 用于映射文件或其他对象的内存区域,如共享库等。

2. C语言中动态内存管理方式

1.malloc(size_t size)

  1. 分配指定大小的内存块。
  2. 返回指向分配的内存块的指针,如果分配失败则返回NULL
  3. 分配的内存块的内容是未初始化的。

2.calloc(size_t num, size_t size)

  1. 分配足够的内存来容纳num个大小为size的元素,并将所有位初始化为0。
  2. 实际上,calloc会先计算总大小(num * size),然后分配内存并初始化。
  3. 如果分配成功,返回指向分配的内存块的指针,否则返回NULL

3.realloc(void *ptr, size_t size)

  1. 重新分配内存块,以适应新的size
  2. 如果ptrNULL,则行为与malloc相同。
  3. 如果size是0,且ptr不是NULL,则释放ptr指向的内存块,并返回NULL
  4. 如果分配成功,返回指向新的内存块的指针,否则返回NULL。注意,新的内存块可能不在原来的位置。

4.free(void *ptr)

  1. 释放之前由malloccallocrealloc分配的内存块。
  2. 如果ptrNULL,则不执行任何操作。
  3. 调用free后,指向已释放内存的指针成为悬空指针(dangling pointer),应避免使用。
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 使用malloc分配内存
    int *p = (int*)malloc(10 * sizeof(int));
    if (p == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // 使用calloc分配并初始化内存
    int *q = (int*)calloc(10, sizeof(int));
    if (q == NULL) {
        free(p); // 释放之前分配的内存
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    // 使用realloc调整内存大小
    p = (int*)realloc(p, 20 * sizeof(int));
    if (p == NULL) {
        free(q); // 释放之前分配的内存
        fprintf(stderr, "Memory reallocation failed\n");
        return 1;
    }

    // 使用内存
    for (int i = 0; i < 20; ++i) {
        p[i] = i;
    }

    // 释放内存
    free(p);
    free(q);

    return 0;
}

 

3. C++中动态内存管理  

1.new 和 delete 关键字

  • new:用于动态分配内存并调用构造函数来初始化对象。
  • delete:用于释放new分配的内存,并调用析构函数来清理对象。

2.new[] 和 delete[] 关键字

  • new[]:用于动态分配内存并初始化数组。
  • delete[]:用于释放new[]分配的内存,并清理数组中的每个元素。
#include <iostream>

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructor called.\n"; }
    ~MyClass() { std::cout << "MyClass destructor called.\n"; }
};

int main() {
    // 使用new分配单个对象的内存
    MyClass* myObject = new MyClass();
    
    // 使用new[]分配对象数组的内存
    MyClass* myArray = new MyClass[10];

    // 使用delete释放单个对象的内存
    delete myObject;

    // 使用delete[]释放对象数组的内存
    delete[] myArray;

    return 0;
}

 

4. operator new与operator delete函数

在C++中,operator new 和 operator delete 是用于动态内存分配和释放的底层操作符函数。它们是C++语言提供的全局函数,可以被重载以改变默认的内存分配行为。

operator new

void* operator new(size_t size);
  •  size 是要分配的内存字节数。
  • 返回值是一个指向分配的内存的指针,如果分配失败,则抛出 std::bad_alloc 异常。

 operator delete

void operator delete(void* ptr) noexcept;

 

  • ptr 是指向要释放的内存的指针。
  • 该函数不返回任何值。
  • noexcept 表示这个函数承诺不会抛出异常。

5. new和delete的实现原理

new 的实现原理

  1. 计算所需内存大小

    • 当使用 new 分配内存时,首先会计算所需对象的大小。如果使用了数组,还需要加上额外的空间来存储数组的大小。
  2. 调用 operator new

    • new 操作符会调用 operator new 函数来分配足够的内存。这个函数等同于C语言中的 malloc,但是 operator new 可以被重载以提供自定义的内存分配策略。
    • operator new 的任务是找到一块足够大的内存块,并返回指向这块内存的指针。
  3. 构造对象

    • 一旦内存被分配,new 操作符会调用对象的构造函数来初始化这块内存。构造函数负责初始化对象的成员变量,并执行其他必要的初始化操作。
  4. 返回指针

    • 最后,new 操作符返回一个指向新分配并初始化的内存块的指针。
T* new() {
    size_t size = sizeof(T); // 计算对象大小
    void* memory = operator new(size); // 分配内存
    T* ptr = static_cast<T*>(memory); // 类型转换
    ptr->T::T(); // 调用构造函数
    return ptr; // 返回指针
}

 delete 的实现原理

  1. 调用析构函数

    • 当使用 delete 释放内存时,首先会调用对象的析构函数。析构函数负责释放对象拥有的资源,比如打开的文件、网络连接等。
  2. 调用 operator delete

    • 在析构函数执行完毕后,delete 操作符会调用 operator delete 函数来释放对象的内存。这个函数等同于C语言中的 free,但是 operator delete 也可以被重载。
    • operator delete 的任务是释放之前由 operator new 分配的内存块。
void delete(T* ptr) {
    ptr->~T(); // 调用析构函数
    operator delete(ptr); // 释放内存
}

 

6. 定位new表达式(placement-new)

定位 new 表达式(placement-new)是 C++ 中 new 操作符的一种特殊形式,它允许在已经分配的内存中构造对象。这种形式的 new 不需要额外分配内存,而是直接在指定的内存地址上构造对象。

定位 new 的语法

new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
  • place_address 是一个指针,指向已经分配的内存地址,对象将在该地址上构造。
  • type 是要构造的对象的类型。
  • initializers 是用于初始化对象的初始值列表。
  • size 是数组的大小,如果构造的是数组。

定位 new 的用途

定位 new 主要用于以下几种情况:

  • 在预分配的内存块中构造对象,这在需要精确控制内存布局时非常有用。
  • 在自定义的内存池中构造对象,可以提高内存分配和释放的效率。
  • 在特定的硬件设备或内存区域中构造对象,例如在嵌入式系统中。
  • 在特定的硬件设备或内存区域中构造对象,例如在嵌入式系统中。

定位 new 的实现原理

定位 new 的实现通常涉及以下步骤:

  1. 确定构造对象的内存地址。
  2. 在该地址上调用构造函数来初始化对象。
#include <new> // 包含定位 new 的定义

class MyClass {
public:
    MyClass() { std::cout << "MyClass constructed" << std::endl; }
    ~MyClass() { std::cout << "MyClass destructed" << std::endl; }
};

int main() {
    char memory[ sizeof(MyClass) ]; // 分配足够的内存来存储 MyClass 对象
    MyClass* myClassPtr = new (memory) MyClass(); // 在分配的内存上构造 MyClass 对象

    // 使用对象...
    
    myClassPtr->~MyClass(); // 手动调用析构函数来销毁对象
    return 0;
}

 


网站公告

今日签到

点亮在社区的每一天
去签到