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;
}