在C和C++中,高效的内存管理是编写性能优化和资源高效利用程序的关键。本文将深入探讨C/C++内存管理的各个方面,包括内存的分布、C语言和C++中的动态内存管理方式,以及new
和delete
操作符的使用
C/C++内存分布
C和C++程序的内存可以分为以下几个区域:
- 栈(Stack):自动存储局部变量。当函数被调用时,局部变量在栈上创建,函数返回时,这些变量被销毁。
- 堆(Heap):用于动态内存分配。与栈不同,堆上的内存分配和释放需要手动管理。
- 全局/静态存储区:存放全局变量和静态变量。这部分内存在程序启动时分配,在程序结束时释放。
- 文字常量区:常量字符串等常量数据存放的地方。它们在程序生命周期内不变。
- 程序代码区:存放程序执行代码的内存区域。
理解这些区域对于避免内存泄漏和优化程序性能至关重要。
C语言中动态内存管理方式
C语言提供了几种动态内存管理的方法,包括:
malloc
:分配指定大小的内存块。分配的内存未初始化,可能包含垃圾数据。calloc
:分配并初始化指定数量的内存块。初始化为零。realloc
:重新分配之前分配的内存块的大小。free
:释放之前分配的内存块。
这些函数允许程序在运行时根据需要分配和释放内存,但也需要开发者负责管理这些内存,以避免内存泄漏。
cpp
#include <stdlib.h>
void dynamicMemoryExample() {
int* ptr = (int*)malloc(sizeof(int) * 4); // 分配内存
if (ptr != NULL) {
for (int i = 0; i < 4; i++) {
ptr[i] = i; // 使用内存
}
}
free(ptr); // 释放内存
}
C++中动态内存管理
C++提高了动态内存管理的抽象层次,通过new
和delete
操作符提供更为直观和安全的方式来处理内存。
- 使用
new
分配内存不仅会分配内存,还会调用对象的构造函数,这提供了初始化对象的机会。 - 使用
delete
释放内存时,会调用对象的析构函数,然后释放内存,这提供了清理资源的机会
cpp
#include <iostream>
void dynamicMemoryExampleInCpp() {
int* ptr = new int(10); // 动态分配内存并初始化
std::cout << *ptr << std::endl; // 使用内存
delete ptr; // 释放内存,并调用析构函数
}
cpp
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
}
注意:申请和释放单个元素的空间,使用 new 和 delete 操作符,申请和释放连续的空间,使用
new[] 和 delete[] ,注意:匹配起来使用。
new和delete****操作自定义类型
cpp
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
// new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
还会调用构造函数和析构函数
A* p1 = (A*)malloc(sizeof(A));
A* p2 = new A(1);
free(p1);
delete p2;
// 内置类型是几乎是一样的
int* p3 = (int*)malloc(sizeof(int)); // C
int* p4 = new int;
free(p3);
delete p4;
A* p5 = (A*)malloc(sizeof(A)*10);
A* p6 = new A[10];
free(p5);
delete[] p6;
return 0;
}
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与
free不会。
operator new与operator delete函数
在C++中,new
和delete
不仅仅是操作符,它们背后实际上是调用了operator new
和operator delete
函数。这两个函数负责分配和释放动态内存。重要的是,C++允许开发者重载这些函数来提供自定义的内存管理策略。
自定义内存管理
自定义内存管理通常用于优化性能,例如通过实现特定的内存池来避免频繁地向操作系统请求小块内存。这样可以显著减少内存碎片以及提高内存分配和释放的效率。
cpp
#include <iostream>
#include <cstdlib>
void* operator new(size_t size) {
std::cout << "Custom new for size: " << size << std::endl;
return malloc(size);
}
void operator delete(void* memory) noexcept {
std::cout << "Custom delete" << std::endl;
free(memory);
}
通过这个简单的例子,我们看到了如何拦截所有的new
和delete
调用,以便在分配和释放内存时执行自定义逻辑
new和delete的实现原理
new
操作符在底层调用operator new
函数来分配内存,并在成功分配内存后调用对象的构造函数。相似地,delete
操作符首先调用对象的析构函数,然后调用operator delete
函数来释放内存。
这种分离使得new
和delete
不仅仅关注内存的分配和释放,还能确保对象生命周期的正确管理。
如果申请的是内置类型的空间, new 和 malloc , delete 和 free 基本类似,不同的地方是:
new/delete 申请和释放的是单个元素的空间, new[] 和 delete[] 申请的是连续空间,而且 new 在申
请空间失败时会抛异常, malloc 会返回 NULL
自定义类型
new 的原理
- 调用 operator new 函数申请空间
- 在申请的空间上执行构造函数,完成对象的构造
delete 的原理
- 在空间上执行析构函数,完成对象中资源的清理工作
- 调用 operator delete 函数释放对象的空间
new T[N] 的原理
- 调用 operator new[] 函数,在 operator new[] 中实际调用 operator new 函数完成 N 个对
象空间的申请
- 在申请的空间上执行 N 次构造函数
delete[] 的原理
- 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理
- 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释
放空间
也就是说当调用delete[]的时候,地址的第一个字节处存放的是n,也就是需要调用n次析构,如果我们使用的是delete,默认从当前位置开始析构,那么位置就会出错。
定位new表达式(placement-new)
定位new表达式是C++中的一个高级特性,允许在已分配的内存上构造对象。这对于内存池、缓存管理等场景非常有用,因为它允许重复使用已分配的内存来创建新对象,避免了频繁的内存分配和释放操作。
cpp
#include <new> // 必须包含这个头文件
char buffer[1024]; // 预分配内存
void placementNewExample() {
int* p = new (buffer) int(10); // 在buffer上构造int对象
// 使用p...
}
使用定位new表达式时,需要确保操作的内存足够大且正确对齐,以容纳指定类型的对象。
常见面试题
内存区域划分
问:请描述C/C++程序中的内存区域划分。
答:C/C++程序的内存分为栈、堆、全局/静态存储区、文字常量区和程序代码区。
动态内存管理
问:malloc
和new
的区别是什么?
答:malloc
仅分配内存,不调用构造函数;new
分配内存的同时调用构造函数初始化对象。相应地,free
与delete
的区别在于delete
会先调用析构函数再释放内存。
定位new
问:什么是定位new表达式,它有什么用途?
答:定位new表达式允许在已分配的内存上构造对象。这在需要在特定位置创建对象,或者在内存池、缓存管理等场景下重复使用内存时非常有用。
通过本文,我们深入探讨了C/C++中的内存管理,从基本的内存区域划分到高级特性如定位new表达式,以及如何通过重载operator new
和operator delete
来实现自定义内存管理策略。理解这些概念不仅对于写出高性能的C/C++代码至关重要,也是面试中常见的问题。希望这篇博客能帮助你在C/C++内存管理方面达到新的高度!