一、内存区域划分
C++中,程序的内存区域从低地址到高地址划分如下:
- 代码段:存储可执行程序的代码和只读常量
- 数据段:存储已初始化的全局变量和静态变量
- 堆:用于程序运行时动态内存分配,从低地址向高地址增长
- 栈:又叫堆栈,存储非静态局部变量/函数参数和返回值等,从高地址向低地址增长
声明 char char2[] = "abcd";
时,实际上是在栈上分配了一个足够大的字符数组,并将字符串字面量 "abcd"
的内容复制到这个数组中。因此,数组 char2
和它的内容都存储在栈区。这是因为 char2
是一个局部变量,而局部变量通常(但不总是)存储在栈上。
声明 char* pChar3 = "abcd";
时,并没有在栈上分配一个包含 "abcd"
的数组。相反,只是创建了一个指向字符串字面量 "abcd"
的指针 pChar3
。这个指针 pChar3
本身存储在栈区,因为它是一个局部变量。但是,字符串字面量 "abcd"
通常存储在只读数据段(也称为代码段或文本段,尽管这个术语可能因编译器和操作系统的不同而有所差异)。这是因为字符串字面量在编译时就已知,并且它们在程序的整个生命周期内都不会改变。因此,编译器将它们放在只读数据段中,以确保它们不会被意外修改。
二、c++的内存管理方式
2.1 malloc/free
C++兼容C语言,所以C语言的内存管理方式在C++中可以正常使用,
malloc函数和free函数可以处理内置类型,不能处理自定义类型
malloc不能实现初始化
malloc申请空间失败返回NULL
2.2 new/delete
new和delete不是函数,而是用户进行动态内存申请和释放的操作符。
2.2.1 底层函数调用
new:
- new操作符底层调用operator new函数申请空间,如果操作对象是自定义类型的变量,还会在申请的空间上调用构造函数,完成对象的构造。
- 其中operator new是系统提供的全局函数,是通过malloc来申请空间的,如果malloc申请空间成功就直接返回,如果失败则执行用户提供的应对措施,如果用户提供该措施则继续申请空间,否则抛出异常。
- 由此可见new操作符在处理内置类型变量时和malloc函数没有太大的区别,除了new能够初始化申请的空间,malloc不能。
delete:
- 在空间上调用析构函数,完成对象中资源的清理工作,如果操作对象是自定义类型的变量,还会调用operator delete函数释放对象的空间。其中系统提供的全局函数operator delete最终是通过free来释放空间的。同样可见delet操作符在处理内置类型变量时和free函数没有太大的区别
(operator new和operator delete函数的实现可见operator new实际上是通过malloc来申请空间,operator delete最终是通过free来释放空间的。)
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);
}
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;
}
new T[N]:
- 调用operator new[]函数,而operator new[]函数实际上又会调用operator new函数完成N个T类型对象的空间申请,如果为自定义类型变量,则在申请的空间上执行N次构造函数
delete[]:
- 如果为自定义类型变量,则在空间上执行N次析构函数,完成N个对象的资源清理调用operator delete[]函数,而operator delete[]函数又会调用operator delete
2.2.2 初始化
2.3定位new
目的:在C++定位new(也称为placement new)是一new表达式,它允许你在已分配的内存上构造对象,而不是像普通的new表达式那样自动分配内存。定位new
主要用于在已经存在的内存块(例如,通过malloc
、calloc
、realloc
或先前通过new[]、operator new
分配的内存块)上构造对象。
定义:1.定位new的语法如下:new (place_address) Type(initializer_list);
这里place_address
是你要在其上构造对象的内存地址,而Type
是要构造的对象的类,initializer_list
是传递给对象构造函数的参数列表(如果对象不需要初始化,则可以省略此部分)。
2.内置类型用定位new并不常见,因为内置类型不需要调用构造、析构函数。
3.使用场景
-
内存池管理:在需要频繁分配和释放小对象的场景中,使用内存池可以减少内存分配的开销。定位
new
允许在内存池分配的内存块上直接构造对象。 -
与C代码交互:当C++代码需要与C代码交互,且C代码已经分配了内存时,可以使用定位
new
在这些内存上构造C++对象。 -
优化性能:在某些性能敏感的应用程序中,通过减少内存分配的次数来提高性能。
4.定位new一般不能调用delete/delete[]来销毁,需要手动调用析构函数销毁对象,再释放空间。因为delete
操作符会尝试释放与对象相关联的内存,但这块内存通常不是通过new
操作符分配的,而是由用户(或库)通过其他方式(如malloc
、new char[]
等)分配的。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class A {
public:
A(int a = 0, int b = 0)
:_a(a)
, _b(b)
{
cout << "A()" << endl;
}
~A() {
cout << "~A()" << endl;
}
void Print() {
cout << _a << "," << _b << endl;
}
private:
int _a;
int _b;
};
int main() {
A* p1 = (A*)malloc(sizeof(A));
new(p1)A;
p1->~A();
free(p1);
//也可以operator delete(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A;
p2->~A();
operator delete(p2);
//也可以free(p2);
//通常不会这样做
A* p3 = new A;//申请空间,构造对象
new(p3)A(1, 2);//在申请的空间上构造对象,第二个对象覆盖了第一个对象
delete p3;
//因为p1指向的空间是由new申请
//所以可以调用delete
char* p5 = new char[sizeof(A)];
//申请一个A的对象大小的空间,不会创造对象
A* p6 = new(p5)A;//在p5指向的空间构造一个对象
//但是p5始终是char*类型,所以创造A*类型的p6
p6->~A();
free(p6);
return 0;
}