1. C/C++内存分布
在C和C++中,程序的内存分布通常包括以下几个部分:
1.代码区(Text Segment)
- 存放程序的机器指令,即程序的可执行代码。
- 这是只读区域,防止程序由于错误而修改自身的指令。
2.全局初始化数据区(Data Segment)
- 存放程序中已经初始化的全局变量和静态变量。
- 这些变量在程序启动时分配,并在程序结束时释放。
3.全局未初始化数据区(BSS Segment)
- 存放程序中未初始化的全局变量和静态变量。
- 这个区域在程序启动时会被初始化为0或空,并在程序结束时释放。
4.栈区(Stack)
- 用于存放函数调用时的局部变量(包括函数参数、返回地址等)。
- 栈是后进先出(LIFO)的数据结构,由系统自动管理。
- 栈的大小通常是有限的,如果栈空间不足,可能会发生栈溢出。
5.堆区(Heap)
- 动态内存分配的区域,用于存放程序运行时动态分配的内存。
- 堆的大小不是固定的,可以根据需要进行扩展(直到系统资源耗尽)。
- 堆内存的管理需要程序员手动进行,使用
malloc、free(C语言)或new、delete(C++语言)等函数。
6.常量区
- 存放程序中定义的常量,如字符串常量等。
- 这部分内存通常也是只读的。
7.程序堆(Heap)(与堆区不同)
- 在某些操作系统中,程序堆是堆区的另一种说法,但在其他情况下,它可能指的是操作系统为程序分配的内存空间,用于存储动态分配的数据。
8.内存映射区域
- 用于映射文件或其他对象的内存区域,如共享库等。
2. C语言中动态内存管理方式
1.malloc(size_t size)
- 分配指定大小的内存块。
- 返回指向分配的内存块的指针,如果分配失败则返回
NULL。 - 分配的内存块的内容是未初始化的。
2.calloc(size_t num, size_t size)
- 分配足够的内存来容纳
num个大小为size的元素,并将所有位初始化为0。 - 实际上,
calloc会先计算总大小(num * size),然后分配内存并初始化。 - 如果分配成功,返回指向分配的内存块的指针,否则返回
NULL。
3.realloc(void *ptr, size_t size)
- 重新分配内存块,以适应新的
size。 - 如果
ptr是NULL,则行为与malloc相同。 - 如果
size是0,且ptr不是NULL,则释放ptr指向的内存块,并返回NULL。 - 如果分配成功,返回指向新的内存块的指针,否则返回
NULL。注意,新的内存块可能不在原来的位置。
4.free(void *ptr)
- 释放之前由
malloc、calloc或realloc分配的内存块。 - 如果
ptr是NULL,则不执行任何操作。 - 调用
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 的实现原理
计算所需内存大小:
- 当使用
new分配内存时,首先会计算所需对象的大小。如果使用了数组,还需要加上额外的空间来存储数组的大小。
- 当使用
调用 operator new:
new操作符会调用operator new函数来分配足够的内存。这个函数等同于C语言中的malloc,但是operator new可以被重载以提供自定义的内存分配策略。operator new的任务是找到一块足够大的内存块,并返回指向这块内存的指针。
构造对象:
- 一旦内存被分配,
new操作符会调用对象的构造函数来初始化这块内存。构造函数负责初始化对象的成员变量,并执行其他必要的初始化操作。
- 一旦内存被分配,
返回指针:
- 最后,
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 的实现原理
调用析构函数:
- 当使用
delete释放内存时,首先会调用对象的析构函数。析构函数负责释放对象拥有的资源,比如打开的文件、网络连接等。
- 当使用
调用 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 的实现通常涉及以下步骤:
- 确定构造对象的内存地址。
- 在该地址上调用构造函数来初始化对象。
#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;
}