目录
前言:C/C++内存分布编辑
一、C++内存管理方式
1、内置类型
2、自定义类型
3、初始化
二、 辨析变量存储位置
三、operator new与operator delete函数
四、new和delete的实现原理
1、内置类型
2、自定义类型
3、结论:
五、定位new表达式
六、malloc/free和new/delete的区别
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
内置类型分配空间C和C++方式如下 :
int main()
{
int* pp1 = (int*)malloc(sizeof(int));
int* p1 = new int;
int* pp2 = (int*)malloc(sizeof(int) * 10);
int* p2 = new int[10];
return 0;
}
释放空间也有所区别:
int main()
{
int* pp1 = (int*)malloc(sizeof(int));
int* p1 = new int;
free(pp1);
delete p1;
int* pp2 = (int*)malloc(sizeof(int)*10);
int* p2 = new int[10];
free(pp2);
delete[] p2;
return 0;
}
C++的new和delete专门为自定义类型而生。
new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间,还会调用构造函数和析构函数
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* pp3 = (A*)malloc(sizeof(A));
free(pp3);
A* p3 = new A(1);
delete p3;
return 0;
}
在C++中,
new
和malloc
都用于动态分配内存,但它们有一些关键的区别:
构造和析构调用:
new
运算符分配内存时,会调用对象的构造函数来初始化对象。malloc
函数分配内存时,只是分配了一块原始的内存块,没有调用任何构造函数。你需要手动调用构造函数来初始化对象。大小计算:
new
运算符知道要为哪种类型的对象分配内存,因此它会自动计算所需的大小,并分配足够的空间。malloc
函数只是分配指定大小的原始内存块,不考虑所分配内存的类型。你需要手动指定大小。类型安全:
new
是类型安全的,因为它在分配内存的同时会调用对象的构造函数。malloc
不关心内存中保存的是什么类型的数据,因此它不提供类型安全性。你需要自己确保在分配的内存上正确地构造和使用对象。返回类型:
new
返回指定类型的指针。malloc
返回 void*
,需要进行类型转换。以后我们动态分配空间就用new与delete配套使用,比如下面的链表的节点就可以使用。
struct ListNode
{
int _val;
ListNode* _next;
ListNode(int val)
:_val(val)
, _next(nullptr)
{}
};
int main()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
return 0;
}
使用new动态分配空间时,也可以初始化。
int main()
{
//int* p1 = new int; // 不会初始化
int* p1 = new int(10);// 申请一个int,初始化10
int* p2 = new int[10];// 申请10个int的数组
int* p4 = new int[10] {1, 2, 3, 4};
return 0;
}
在调试中可以观察到初始化情况:
在C/C++中,变量的存储位置取决于它们的生命周期和作用域。全局变量和静态变量存储在数据段,它们的生命周期是整个程序的运行时间。局部变量和函数参数存储在栈上,它们的生命周期是函数的执行时间。动态分配的内存存储在堆上,它的生命周期由程序员控制,使用malloc, calloc或realloc分配,使用free释放。字符串常量存储在代码段,它的生命周期是整个程序的运行时间。
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);
}
globalVar
在数据段(静态区)。全局变量存储在数据段。staticGlobalVar
也在数据段(静态区)。静态全局变量也存储在数据段。staticVar
在数据段(静态区)。函数内的静态变量存储在数据段。localVar
在栈上。函数内的局部变量存储在栈上。num1
在栈上。数组是局部变量,存储在栈上。char2
在栈上。字符数组是局部变量,存储在栈上。pChar3
在栈上,但它指向的字符串"abcd"在代码段(常量区)。指针变量本身是局部变量,存储在栈上,但它指向的字符串常量存储在代码段。ptr1
在栈上,但它指向的内存在堆上。指针变量本身是局部变量,存储在栈上,但通过malloc分配的内存存储在堆上。*char2
在栈上。它是字符数组的第一个元素,存储在栈上。*pChar3
在代码段(常量区)。它是字符串常量的第一个字符,存储在代码段。*ptr1
在堆上。它是通过malloc分配的内存的第一个元素,存储在堆上。
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
int main()
{
// 失败了抛异常
int* p1 = (int*)operator new(sizeof(int*));
// 失败返回nullptr
int* p2 = (int*)malloc(sizeof(int*));
if (p2 == nullptr)
{
perror("malloc fail");
}
return 0;
}
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
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;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << endl;
}
~A()
{
cout << "~A():" << endl;
}
private:
int _a;
};
int main()
{
// 申请空间 operator new -> 封装malloc
// 调用构造函数
A* p5 = new A;
cout << endl;
// 先调用析构函数
// 再operator delete p5指向的空间
// operator delete -> free
delete p5;
cout << endl;
// 申请空间 operator new[] ->perator new-> 封装malloc
// 调用10次构造函数
A* p6 = new A[10];
cout << endl;
// 先调用10次析构函数
// 再operator delete[] p6指向的空间
delete[] p6;
cout << endl;
return 0;
}
输出结果:
下面两种情况可以正常运行
int* p7 = new int[10];
free(p7); // 正常释放
A* p8 = new A;
free(p8); // 没有调用析构函数,不需要资源清理。
自定义类型的成员变量如果需要资源清理,则一定要使用delete。
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
_a = new int[4];
_top = 0;
_capacity = 4;
}
~Stack()
{
cout << "~Stack()" << endl;
delete[] _a;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st;//自定义类型调用构造
Stack* pst = new Stack;//指针是内置类型
delete pst;
//free(pst); // 使用free则少调用了析构函数,会造成内存泄漏
return 0;
}
st
是一个自定义类型的对象,它是通过调用 Stack
类的构造函数创建的。在 main
函数中,Stack st;
语句会调用 Stack
类的默认构造函数 Stack()
。构造函数会分配一个包含 4 个整数的动态数组 _a
,并将 _top
和 _capacity
初始化为 0 和 4。构造函数执行完毕后,st
对象就被创建并可以使用。
pst
是一个指向 Stack
类型对象的指针,它是通过使用 new
运算符动态分配内存创建的。在 main
函数中,Stack* pst = new Stack;
语句会调用 Stack
类的默认构造函数 Stack()
,并返回一个指向新创建的 Stack
对象的指针。通过 new
创建的对象在堆上分配内存,因此需要使用 delete
运算符手动释放内存。在代码的最后,delete pst;
语句会调用 Stack
类的析构函数 ~Stack()
,释放 pst
指向的对象所占用的内存,并将指针置为无效。
free
函数释放通过 new
分配的内存是不正确的。free
函数是用于释放通过 malloc
、calloc
或 realloc
分配的内存,而不是用于释放通过 new
分配的内存。如果使用 free(pst)
,则会导致析构函数未被调用,从而造成内存泄漏。在delete pst中,先调用析构函数,再调用operator delete(pst)。
new/malloc系列 有底层实现机制有关联交叉。不匹配使用可能有问题,也可能没问题,建议大家一定匹配使用 。
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
// 定位new/replacement new
int main()
{
// p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,
// 因为构造函数没有执行
A* p1 = (A*)malloc(sizeof(A));
new(p1)A; // 注意:如果A类的构造函数有参数时,此处需要传参
p1->~A();
free(p1);
A* p2 = (A*)operator new(sizeof(A));
new(p2)A(10);
p2->~A();
operator delete(p2);
return 0;
}