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个intfree:用于释放通过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++底层机制,实现高效的内存管理。