C/C++动态内存管理

在 C 和 C++ 中,对于动态内存的管理是一个永恒的话题,C语言采用 x(x)alloc 和 free 来开辟和释放动态内存空间,不同的是,C++ 引入了 new 和 delete 操作符作为新的方式。这二者有什么区别呢?new \ delete 又有哪些高级用法,这些都将在下文一一列出。

malloc \ free \ calloc \ realloc

malloc

1
void* malloc(size_t size);

开辟一段 size 字节大小的内存空间,并将空间的首地址返回,如果 size 为 0 ,则返回空指针。

free

1
void free(void* p);

释放一段指针指向的内存空间。

calloc

1
void* calloc(size_t nmemb, size_t size);

开辟一段连续的内存空间,它的大小为 nmemb * size 字节,并为每一块内存地址赋 0,它实际上依然调用了 malloc 只是在 malloc 的基础上进行了额外的操作。

realloc

函数原型

1
void* realloc(void* ptr, size_t size);

重新开辟一段内存空间,将传入的指针 ptr 所指地址的空间调整至 size 大小。如果 size 比原来的空间大,会在另一个地址处开辟一段新的空间,而 ptr 所指的空间被释放;否则,返回的地址仍然是原先的地址,只是合法空间的大小经过了调整。不过即使 realloc 可能返回与 ptr 相同的首地址,但大多数情况下仍然要以下面的方式来使用 realloc 从而避免多次释放的问题。

1
2
3
4
5
6
7
int main()
{
int *p = (int*)malloc(5*sizeof(int));
p = realloc(p, 10*sizeof(int)); // 传入参数 p 的地址在 realloc 中已经被释放
free(p);
return 0;
}

new \ delete

new 和 delete 是 C++ 中新增的动态内存管理的两个运算符,下面是 new 和 delete 的用例。

1
2
3
4
5
6
7
8
int main()
{
int *p1 = new int(0); // 一个整形空间,初始化为 0
int *p2 = new int[5]; // 五个整形空间
delete p1; // 释放单个空间
delete[] p2; // 释放连续空间
return 0;
}

两种方式的区别

下表总结了两种方式之间的区别

特征 new \ delete malloc \ free 解释
分配内存的位置 自由存储区 堆区 new 和 delete 可以重载,分配到那个存储区取决于具体实现。
返回指针的类型 完整的类型 void* new 开辟出的内存空间不用像 malloc 那样做类型转换,使用起来更方便
分配失败的处理 抛出异常 返回NULL 抛出异常处理起来更加灵活
分配内存的大小 由编译器根据类型计算 必须显示指定字节数
处理连续空间 new[] 必须用户计算数组大小后进行分配
已分配内存的扩充 无法直观地处理 使用 realloc 完成
是否相互调用 可以,取决于重载的实现 不可调用 new 和 delete
分配内存时内存不足 无法通过用户代码处理 能够使用 realloc 函数或重新制定分配器
函数重载 允许 不允许
构造与析构 调用 不调用 new 和 delete 为对象开辟/释放空间时会调用对应的构造、析构函数,这在面向对象的程序中极为重要

operator new \ placement new \ operator delete

上面我们说的 new 运算符是 new operator 而不是 operator new

这两者的区别在于 new operator 的行为是不可以被改变的,它稳定地完成两个任务:

  1. 分配内存(调用的是 operator new
  2. 调用构造函数初始化

同样 delete operator 也是一样的道理

所谓的重载 newdelete 其实重载的是 operator newoperator delete 而不是 new operatordelete operator

operator new

下面是几个 operator new 的原型(定义在 new 文件中)

1
2
3
4
5
6
// 普通 operator new 版本,如果内存开辟失败,会抛出异常
void* operator new (std::size_t size) throw (std::bad_alloc);
void* operator new[] (std::size_t size) throw (std::bad_alloc);
// 第二个参数没有实际的传输数据,它的作用在于标识这个函数不会抛出异常
void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();
void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_constant) throw();

如果要使用第二个版本的 operator new,需要在使用 new operator时按下面的方式使用。

1
2
3
4
5
6
int main()
{
int *p = new(std::nothrow) int(0);
delete p;
return 0;
}

同样,我们可以自己重写或者重载 operator new,如下:

1
2
3
4
5
6
7
8
9
10
11
12
void* operator new(size_t size, const char* str)
{
cout << "operator new " << str << endl;
return malloc(size);
}

int main()
{
int* p = new("hello") int(3);
delete []p;
return 0;
}

程序执行结果为:

operator new hello ..

placement new

new 文件中,还定义了一个类型的 operaotr new,原型如下:

1
2
3
4
5
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

这便是 placement new,它的作用在于在一个已经分配的内存空间上构造一个对象,其语法如下:

1
2
3
4
5
6
7
8
9
int main()
{
char a[4] = {1, 1, 0, 0};
cout << hex << &a << dec << endl;
int *p = new(&a) int;
cout << hex << p << dec << endl;
cout << *p << endl;
return 0;
}

程序执行结果为:

0x7ffd738daa24
0x7ffd738daa24
257

operator delete

同样,operator delete 也可以像 operator new 那样进行重载操作,如下:

1
2
3
4
5
6
7
8
9
10
11
12
void operator delete(void* ptr)
{
cout << "operator delete" << endl;
free(ptr);
}

int main()
{
int *p = new int;
delete(p);
return 0;
}

执行结果为:

operator delete