C++中的内存管理是编写高效、可靠程序的关键所在。C++不仅继承了C语言的内存管理方式,还增加了面向对象的内存分配机制,使得内存管理既有灵活性,也更加复杂。学习内存管理不仅有助于提升程序效率,还有助于理解计算机的工作原理和资源分配策略。
在编译和执行C++程序时,内存划分为几个不同的区域,各个区域承担不同的任务。以下是C++内存的基本分布:
1.栈(Stack):
2.堆(Heap):
3.数据段(Data Segment):
4.代码段(Text Segment):
#include <cstdlib> // 包含 malloc, calloc, realloc, free
#include <iostream>
int globalVar = 1; // 全局变量,存储在已初始化数据段
static int staticGlobalVar = 1; // 静态全局变量,存储在已初始化数据段
void Test() {
static int staticVar = 1; // 静态局部变量,存储在已初始化数据段
int localVar = 1; // 局部变量,存储在栈中
int num1[10] = {1, 2, 3, 4}; // 局部数组,存储在栈中
char char2[] = "abcd"; // 字符数组,存储在栈中(字符串内容复制到栈中)
const char* pChar3 = "abcd"; // 指针变量在栈中,指向的字符串常量"abcd"在常量区
int* ptr1 = (int*)malloc(sizeof(int) * 4); // 动态分配的内存,存储在堆中
int* ptr2 = (int*)calloc(4, sizeof(int)); // 动态分配并初始化的内存,存储在堆中
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4); // 调整内存大小,存储在堆中
free(ptr1); // 释放堆上的内存
free(ptr3); // 释放堆上的内存
}
int main() {
Test();
return 0;
}
staticVar
是一个静态局部变量,虽然它是局部变量,但因为是 static
类型,它的存储位置与普通局部变量不同。staticVar
存储在 已初始化数据段,即使函数退出,变量也不会被释放,而是保留其值,直到程序结束。如果函数再次调用,staticVar
不会重新分配内存,而是继续使用上一次保留的值。localVar
是一个局部变量,num1
是一个局部数组,它们都存储在 栈 中。栈用于存储局部变量、函数参数等,栈内存是随着函数的调用自动分配的,函数返回时栈上的内存会自动释放。局部变量的生命周期仅限于函数执行期间,函数返回后它们会被销毁。char2
是一个字符数组,存储在 栈 中。编译器会将字符串 "abcd"
复制到栈中,这意味着 "abcd"
的内容实际上存在栈内存中。由于 char2[]
是局部变量,因此它的存储空间(包括字符串内容)在函数调用时分配,在函数返回时释放。pChar3
是一个指针变量,存储在 栈 中。它指向一个字符串常量 "abcd"
,该字符串常量存储在 只读数据段(常量区)。常量区用于存储字符串字面量等只读数据,因此该字符串在程序的整个生命周期内都存在,且不能被修改。ptr1
、ptr2
和 ptr3
这三个指针变量本身存储在 栈 中,指向的内存则存储在 堆 中。这些指针通过动态内存分配函数 malloc
、calloc
和 realloc
分配了堆内存。堆内存用于程序运行时动态分配的数据,需要手动释放。如果这些堆内存未通过 free()
释放,则会导致内存泄漏。
malloc
动态分配了内存,分配的内存位于堆中。
calloc
动态分配了内存,分配的内存位于堆中,且会初始化为零。
realloc
调整了 ptr2
的内存大小,新的内存分配在堆中。
详见前面博客:C语言动态内存管理
在C语言中,动态内存管理通过以下几个函数实现:
malloc(memory allocation):分配指定大小的内存块,内存中的数据未被初始化。返回值是void*
类型指针,需要手动强制转换为具体的类型。
示例:
int* ptr = (int*)malloc(sizeof(int) * 5); // 申请5个int的内存
void*
类型。
示例:
int* ptr = (int*)calloc(5, sizeof(int)); // 申请5个int的内存并初始化为0
realloc(reallocation):用于调整已经分配的内存块大小。传入新大小后,realloc
会尝试扩大或缩小原有的内存块,如果扩展失败,它会在新的位置申请内存并拷贝原内容。
示例:
int* newPtr = (int*)realloc(ptr, sizeof(int) * 10); // 将原来的内存扩展到10个int
free:用于释放通过malloc
、calloc
、realloc
申请的内存。内存释放后,不再受程序管理,避免内存泄漏。
示例:
free(ptr); // 释放动态分配的内存
C++继承了C语言的malloc
、calloc
、realloc
和free
,但提供了更加灵活的内存管理方式,即new
和delete
操作符:
示例:
// 动态申请一个int类型的空间,未初始化,值未定义
int* ptr1 = new int;
// 动态申请一个int类型的空间,并初始化为10
int* ptr2 = new int(10);
// 动态申请3个int类型的连续空间,未初始化,值未定义
int* ptr3 = new int[3];
注意:1. 申请和释放单个元素的空间,使用new和delete操作符 申请和释放连续的空间,使用 new[]和delete[], 2.new和delete要匹配起来使用。
new和delete不仅仅是分配和释放内存,还会自动调用构造函数和析构函数,非常适合面向对象编程中的自定义类型管理。
代码示例:new和delete操作自定义类型
#include <iostream>
#include <cstdlib> // 包含 malloc 和 free
using namespace std;
class A {
public:
// 构造函数
A(int a = 0) : _a(a) {
cout << "A() constructor called, object address: " << this << endl;
}
// 析构函数
~A() {
cout << "~A() destructor called, object address: " << this << endl;
}
private:
int _a; // 成员变量
};
int main() {
// 使用 malloc 申请内存,但不会调用构造函数
A* p1 = (A*)malloc(sizeof(A)); // 只分配内存,未调用构造函数
A* p2 = new A(1); // 分配内存并调用构造函数
// 释放通过 malloc 申请的内存,不会调用析构函数
free(p1);
// 使用 delete 释放内存,会调用析构函数
delete p2;
// 内置类型的操作:malloc 和 new 对内置类型的行为几乎相同
int* p3 = (int*)malloc(sizeof(int)); // 只分配内存,不会初始化
int* p4 = new int; // 分配内存但未初始化
free(p3); // 释放 malloc 分配的内存
delete p4; // 释放 new 分配的内存
// 动态申请数组
A* p5 = (A*)malloc(sizeof(A) * 10); // 只分配内存,未调用构造函数
A* p6 = new A[10]; // 分配内存并调用构造函数
// 释放内存
free(p5); // 只释放内存,不调用析构函数
delete[] p6; // 释放数组并调用每个对象的析构函数
return 0;
}
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
在C++中, new和delete操作符用于动态内存管理,并且会在创建和销毁对象时自动调用构造函数和析构函数。然而 new和delete在底层是依赖于全局的 operator new 和operator delete 函数进行 实际的内存分配和释放操作。通过自定义这些函数,开发者可以控制内存的分配策略,尤其是在需要自定义内存管理(如内存池)时。
operator new
的实现原理operator new
是C++的全局函数,它负责分配内存。默认情况下,它会调用 malloc
来分配指定大小的内存。如果内存分配失败,它会抛出 std::bad_alloc
异常。
代码示例:
#include <new> // 包含 bad_alloc
#include <cstdlib> // 包含 malloc
#include <iostream>
using namespace std;
// operator new: 内存分配函数
void* operator new(size_t size) _THROW1(std::bad_alloc) {
void* p;
// 使用 malloc 分配内存,如果分配失败则尝试调用处理函数
while ((p = malloc(size)) == 0) {
// 如果处理函数返回 0,抛出 bad_alloc 异常
if (_callnewh(size) == 0) {
static const std::bad_alloc nomem;
_RAISE(nomem); // 抛出内存不足异常
}
}
return p; // 如果成功分配内存,返回指向该内存的指针
}
operator delete
的实现原理operator delete
是 new
的对应函数,用于释放内存。它会调用 free
来释放由 new
分配的内存。和 new
类似,delete
也可以被用户自定义,以实现特定的内存管理需求。
代码示例:
#include <cstdlib> // 包含 free
#include <iostream>
using namespace std;
// operator delete: 内存释放函数
void operator delete(void* p) noexcept {
// 如果传入的指针为空,则不执行释放操作
if (p == NULL) return;
// 通过 free 函数释放内存
free(p);
}
operator new[]
与 operator delete[]
与 operator new
和 operator delete
类似,C++ 中还提供了 operator new[]
和 operator delete[]
用于分配和释放数组。这些函数与单个对象的 operator new
和 operator delete
在功能上类似,只是针对的是数组。
代码示例:
// operator new[]: 分配数组所需的内存
void* operator new[](size_t size) {
cout << "Allocating array of size: " << size << endl;
return malloc(size); // 使用 malloc 分配内存
}
// operator delete[]: 释放数组的内存
void operator delete[](void* p) noexcept {
cout << "Freeing array memory" << endl;
free(p); // 使用 free 释放内存
}
new
和 delete
实现原理对于内置类型(例如 int
、double
等),new
和 malloc
,delete
和 free
的行为非常相似。它们之间的主要区别在于:
new/delete
申请和释放单个对象,而 new[]/delete[]
申请和释放数组,即连续的多个元素。new
在内存分配失败时会抛出 std::bad_alloc
异常。malloc
在分配失败时返回 NULL
,所以使用 malloc
时需要手动检查返回值是否为空。new
可以初始化内存。例如,new int(5)
会为新分配的 int
空间初始化为 5,而 malloc
只分配内存,不做初始化。代码示例:
#include <iostream>
#include <cstdlib> // 包含 malloc 和 free
using namespace std;
int main() {
// 使用 malloc 分配内存,不会初始化
int* p1 = (int*)malloc(sizeof(int));
// 使用 new 分配内存,并初始化为 10
int* p2 = new int(10);
// 释放内存,malloc 使用 free 释放
free(p1);
// 释放内存,new 使用 delete 释放
delete p2;
return 0;
}
总结:
new
和 malloc
在申请内存的行为上相似,但 new
提供了异常处理机制,而 malloc
返回 NULL
。new
和 delete
实现原理对于自定义类型(例如类对象),new
和 delete
的行为比内置类型更复杂,因为它们不仅需要分配和释放内存,还必须调用构造函数和析构函数。这是 new/delete
和 malloc/free
的核心区别。
自定义类型
new
的工作流程:
operator new
分配内存: operator new
通过 malloc
或其他内存分配函数为对象分配内存空间。new
调用对象的构造函数,完成对象的初始化。自定义类型
delete
的工作流程:
delete
在释放对象之前,首先会调用对象的析构函数,用于释放对象中的资源(例如释放对象成员变量中动态分配的内存,关闭文件等)。operator delete
释放内存:
operator delete
函数被调用,使用 free
来释放该对象占用的内存空间。代码示例:
#include <iostream>
using namespace std;
class A {
public:
// 构造函数
A(int a = 0) : _a(a) {
cout << "A() constructor called, value: " << _a << endl;
}
// 析构函数
~A() {
cout << "~A() destructor called, value: " << _a << endl;
}
private:
int _a; // 成员变量
};
int main() {
// 使用 new 分配 A 类对象,调用构造函数
A* p1 = new A(10);
// 使用 delete 释放 A 类对象,调用析构函数
delete p1;
return 0;
}
总结:
new
和 delete
:不仅分配和释放内存,还负责调用构造函数和析构函数,确保自定义类型对象的正确初始化和清理。malloc/free
:对于自定义类型来说,只会分配和释放内存,不会调用构造和析构函数,因此不适用于需要自动管理对象生命周期的情况。placement-new
是一种特殊的new
语法,允许在指定的内存地址上构造对象。该特性在高效内存分配的场景(如内存池)中非常有用。
示例:
#include <new> // 必须包含<new>头文件
class Example {
public:
Example() { std::cout << "Example Constructor" << std::endl; }
~Example() { std::cout << "Example Destructor" << std::endl; }
};
int main() {
char buffer[sizeof(Example)]; // 分配足够大的缓冲区
Example* p = new(buffer) Example; // 在缓冲区上构造对象
p->~Example(); // 显式调用析构函数
return 0;
}
在 C++ 中,malloc/free
和 new/delete
都可以用于从堆上申请和释放内存。它们的共同点是都用于动态内存分配,并且需要用户手动释放内存。但它们之间有一些重要的区别:
1. 函数 vs 操作符
malloc/free
:这是 C 语言中的函数,用于分配和释放内存。
new/delete
:这是 C++ 中的操作符,用于分配和释放内存,并且可以调用构造函数和析构函数。
2. 内存初始化
malloc
:只分配内存,不会对分配的内存进行初始化,内存中的数据是未定义的。
new
:分配内存的同时可以初始化对象,尤其是对于自定义类型时,new
会调用构造函数对对象进行初始化。
3. 内存大小计算
malloc
:用户需要手动计算需要分配的内存大小并传递给 malloc
,例如 malloc(sizeof(int))
。
new
:用户不需要手动计算内存大小,new
会根据类型自动计算。例如 new int
自动分配 int
类型的空间。
4. 返回类型
malloc
:返回 void*
类型的指针,使用时必须进行强制类型转换,例如 (int*)malloc(sizeof(int))
。
new
:返回具体类型的指针,不需要强制类型转换,例如 new int
返回 int*
类型的指针。
5. 错误处理
malloc
:内存分配失败时返回 NULL
,因此需要在使用时手动检查返回值是否为 NULL
。
new
:内存分配失败时会抛出 std::bad_alloc
异常,因此使用 new
时需要捕获异常。
6. 构造函数与析构函数
malloc/free
:只负责内存的分配与释放,不会调用构造函数和析构函数。因此,malloc/free
不能正确处理自定义类型的对象。
new/delete
:在分配内存时会调用对象的构造函数完成初始化,在释放内存时会调用对象的析构函数完成资源的清理和释放。因此,new/delete
更适合管理自定义类型的对象。
掌握内存管理是编写高效C++程序的基础。通过熟悉栈、堆和各类动态内存管理方法,可以更好地理解C++底层机制,实现高效的内存管理。