C++——内存管理
目录
引言
C/C++的内存分布
C语言中动态内存管理方式
C++内存管理方式
1.new/delete操作内置类型
2.new与delete操作自定义类型
operator new与operator delete函数
new与delete的实现
1.内置类型
2.自定义类型
定位new表达式
malloc/free和new/delete的区别
结束语
引言
在简单的学习完类与对象之后,我们接下来学习C++的内存管理。
求点赞收藏评论关注!!!
C/C++的内存分布
我们先来看一段代码以及问题:
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";
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);
}
选择题:
问题:以上数据存放在什么位置?
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
1.globalVar 在哪里? C
2.staticGlobalVar 在哪里? C
3. staticVar 在哪里? C
4. localVar 在哪里? A
5.num1 在哪里? A
6.char2 在哪里? A
7.*char2 在哪里? A
8. pChar3在哪里? A
9.*pChar3 在哪里? D
10.ptr1 在哪里? A
11.*ptr1 在哪里? B
解析:
1.globalVar 是全局变量,全局变量放在静态区。 C
2.staticGlobalVar 也是全局静态变量,放在静态区。与globalVar 的区别是: 普通全局变量作用于整个代码,可被其他文件访问或修改。staticGlobalVar 被 static 修饰,被 static 修饰的静态全局变量只作用于当前文件,其他文件不可见。 C
3.staticVar 是局部静态变量,局部静态变量虽然访问作用域在函数中,但它与全局变量一样,存放在静态区。 C
4.localVar 是局部变量,局部变量放在栈。被static修饰的局部变量的生命周期只会在程序结束后结束,而普通的局部变量的生命周期出了当前作用域就会结束。 A
5.num1 是数组名,是一个局部变量,放在栈区。 A
6.char2 也是一个局部变量,放在栈区,常量字符串"abcd"放在代码段(常量区),数组开辟的空间放在栈区。 A
7.*char2 这里表示的是数组首元素的地址,解引用即表示第一个元素 ‘a’。虽然我们知道常量字符串是存储在常量区,但是,数组中存的 “abcd” 是从常量区中 拷贝 过来存储在数组中,依然是在栈上。 A
8.pChar3 是指针变量,是局部变量,是在栈上开辟空间存储 “abcd” 首字符的地址的变量。 A
9.*pChar3 指向 “abcd” 首元素的地址 ‘a’。“abcd” 是只读常量,放在常量区。 D
10.ptr1 与 pChar3 同理,都是存储在栈上。 A
11.ptr1 指向动态开辟的空间,而 *ptr1 则是问动态开辟出的空间存储在哪,存储在堆上。 B
1.栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
2.内存映射段是高效的I/0映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
3.堆用于程序运行时动态内存分配,堆是可以上增长的。
4.数据段--存储全局数据和静态数据,
5.代码段--可执行的代码/只读常量。
C语言中动态内存管理方式
C语言中有关动态内存管理的内容在我的博客:C语言——动态内存管理 中有比较详细的介绍。欢迎各位大佬能阅读一下。
C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理。
1.new/delete操作内置类型
new 运算符用于动态分配内存,并返回指向该内存的指针。它可以与单个对象或对象数组一起使用。
delete 运算符用于释放之前由 new 运算符分配的内存。它必须与 new 运算符成对使用,以避免内存泄漏。
使用示例如下:
int main()
{
// 动态分配一个整型变量的内存,并将地址赋给指针p1
int* p1 = new int;
// 动态分配一个包含10个整型变量的数组的内存,
// 并将数组首地址赋给指针p2
int* p2 = new int[10];
// 注意:这里的数组也是未初始化的,数组中的所有元素都是未定义的。
// 动态分配一个整型变量的内存,并初始化为10,然后将地址赋给指针p3
int* p3 = new int(10);
// 这里,p3指向的整型变量被明确初始化为10。
//动态申请5个int类型空间,并将前面2个初始化为1,后面默认初始化为0
int* p4 = new int[5] {1, 1};
//与C语言free功能类似,释放空间防止内存泄漏
delete p1;
delete[] p2;
delete p3;
delete[] p4;
return 0;
}
我们可以通过监视窗口来观察一下:
2.new与delete操作自定义类型
使用new和delete来操作内置类型(如int、float、char等)时,它们在底层的行为上与C语言中的malloc和free没有太大的本质区别。这主要体现在它们都是用来在堆上分配和释放内存的。然而,它们之间还是存在一些关键的区别和考虑因素,尤其是在处理自定义类型时。
来看看这段代码:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << "_a = " << _a << endl;
}
private:
int _a = 0;
};
int main()
{
A* p1 = new A;
A* p2 = new A(10);
p1->Print();
p2->Print();
delete p1;
delete p2;
return 0;
}
运行结果为:
我们可以得知:new在创建自定义类型时会自动调用其构造函数,delete在释放其空间时会自动调用其析构函数。
operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete全局函数来释放空间。
下面是operator new与operator delete函数的源代码:
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
在C++中,operator new 和 operator delete 是全局函数,它们分别用于内存的申请和释放。这些函数在内部通常会使用C语言风格的内存管理函数(如malloc和free)来执行实际的内存分配和释放操作,但它们提供了额外的功能,特别是异常处理和类型安全。
operator new可以配置为在内存分配失败时抛出一个std::bad_alloc异常,而不是简单地返回nullptr。这允许C++程序以一种更加面向对象和异常安全的方式来处理内存分配失败的情况。
operator delete 最终是通过free来释放空间的。
new与delete的实现
接下来我们来探讨一下new与delete的实现,来看一下这段代码:
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _x = 0;
};
void Test()
{
int* ptr1 = new int; //内置类型
A* ptr2 = new A; //自定义类型
delete ptr1;
delete ptr2;
}
我们通过反汇编语言来观察一下:
1.内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
2.自定义类型
new的原理:
(1)调用operator new函数申请空间
(2)在申请的空间上执行构造函数,完成对象的构造
delete的原理:
(1)在空间上执行析构函数,完成对象中资源的清理工作
(2)调用operator delete函数释放对象的空间
new T[N]的原理:
(1)调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
(2)在申请的空间上执行N次构造函数
delete[]的原理:
(1)在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
(2)调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
定位new表达式
定位new表达式(placement new)是一种特殊的C++语法,允许开发者在已分配但未经初始化的内存区域上构造对象。
定位new的基本使用格式如下:
new (place_address) type;
或者,如果需要传递初始化列表给构造函数,可以这样做:
new (place_address) type(initializer-list);
place_address 是一个指向足够大且已分配(但可能未初始化)的内存块的指针,用于存储新构造的对象。
type 是要构造的对象的类型。
initializer-list 是一个可选的初始化列表,用于传递给对象的构造函数。
下面是个简单的使用示例:
class A
{
public:
A(int a = 0)
: _a(a) // 使用初始化列表显式初始化成员变量_a
{
cout << "A(int a = 0):" << _a << endl;
}
// 析构函数
~A()
{
cout << "~A()\n";
}
private:
int _a;
};
int main()
{
// 分配内存
void* memory = operator new(sizeof(A));
// 使用定位new构造对象:在已分配的内存上构造A类的对象
A* a = new (memory) A;
// 显式调用析构函数
a->~A();
// 释放内存
operator delete(memory);
return 0;
}
输出结果为:
malloc/free和new/delete的区别
(1)malloc和free是函数,new和delete是操作符。
(2)malloc申请的空间不会初始化,new可以初始化。
(3)malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可。
(4)malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
(5)malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
(6)申请自定义类型对象时,malloc / free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
结束语
写的有点墨迹了。。。
求点赞收藏评论关注!!!
感谢各位大佬支持!!!