目录
🙊 C 语言内存管理方式🙊
💖 示例代码
先看以下代码,需不需要 free(p2) ?
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
}
答案是不需要,因为这里已经对 p2 进行扩容了,扩容分为原地扩容和异地扩容,如果源地址有足够的空间,就原地扩容,此时 p2 和 p3 的地址相同,如果 p2 没有足够的空间,就进行异地扩容,将内容拷贝过去再 free 掉 p2。
🙊 C ++ 内存管理方式🙊
💖 new / delete 操作内置类型
C 语言的内存管理机制是返回值为 void* 的函数调用,需要自己去计算大小并强制类型转换,而 c++ 在 c 语言扩容机制基础上添加了自己的扩容机制,使用 new 操作符创建一个对象并用一个指针进行接收。

代码如下:
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
int* ptr7 = new int[10] {1,2,3,4};
delete ptr4;
delete ptr5;
delete[] ptr6;
}
ptr5 是申请了一个 int 空间并初始化为 10,而 ptr6 是申请了 40 个字节的空间用于初始化数组。如果想把数组初始化可以写成 ptr7 这种方式。
从功能上来说,C 语言和 c++ 的功能都是一样的。对于内置类型,使用哪种方式都可以,但是对于自定义类型,就需要用 new 来进行空间的申请。看下面一段代码:
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;
A* pp3 = (A*)malloc(sizeof(A));
free(pp3);
A* p3 = new A(1);
delete p3;
return 0;
}
对自定义类型使用 malloc 只会开空间而不能进行初始化,而使用 new 除了开空间,还调用了构造函数进行初始化。同理 free 只会释放空间,而 delete 除了释放空间,还会调用析构函数。看下面一段代码体会 new 的方便之处:
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
struct ListNode
{
int _val;
ListNode* _next;
ListNode(int val)
:_val(val)
, _next(nullptr)
{}
};
//ListNode BuyListNode(int x)
//{
// //...
//}
int main()
{
ListNode* n1 = new ListNode(1);
ListNode* n2 = new ListNode(2);
ListNode* n3 = new ListNode(3);
return 0;
}
如果是 C 语言,对于链表创建一个新节点需要单独写一个创建新节点的函数,而在 c++ 中的自定义类型一般使用 new 来开空间同时调用构造函数进行初始化。所以对于默认类型,用哪种方式开辟空间都行,对于自定义类型,就要使用 new 开辟空间。
🙊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);
}
1、globalVar 是一个全局变量,全局变量和静态变量都放在静态区,从系统的角度静态区也叫数据段
2、staticGlobalVar 是一个静态全局变量,静态全局变量和全局变量都放在静态区
3、staticVar 是一个局部静态变量,也是放在静态区的,它们三个作用域不相同,但是其生命周期是整个程序期间都存在
4、localVar 是一个局部变量,存放在栈上
5、num1是数组名,代表整个数组,存放在栈上
6、char2 是一个数组,char2 和 num1 的区别是,num1 是自己确定大小,而 char2 是通过初始化来确定大小,初始化多大,char2 的大小就是多少。所以 char2 存在于栈上
7、char2 由于 char2 是一个数组名,存放数组首元素的地址,而 char2 代表数组的第一个元素,首先在栈上开辟五个字节的数组,将 char2 中常量字符串的内容包含 \0 拷贝到数组中
在使用 sizeof 时,char2 代表整个数组,但是实际上 char2 的值是首元素的地址,所以 * char2 存在于 栈上。
因为 “ a b c d ” 确实存在于常量区,但是 char char2[ ] = “abcd”; 写法的意思是将常量区的内容拷贝到栈上的数组中,而数组开多大是初始化决定的。
8、pchar3 存在于栈上,因为 pchar3 也是一个局部变量存在于栈上,但是注意 pchar3 指向的空间在常量区。
9、pchar3 指向的空间属于代码段* / 常量区
10、ptr1 是一个指针,且指针是一个局部变量,而局部变量都是在栈上,所以 ptr1 存放在栈上
11、ptr1 是 ptr 指向的空间,存放在堆上所以 ptr1 在堆区
12、sizeof(num1) 大小为 40 个字节,因为 sizeof(数组名) 代表的是整个数组**,虽然只给了 4 个值,但是数组大小是固定的 10 个字节,前 4 个位置为 1、2、3、4,后 6 个位置被初始化成 0。
13、sizeof(char2) 大小为 5,因为 sizeof(数组名) 代表数组的大小,这里多开了一个字节的空间给 \0。
14、strlen(char2) 为 4,因为 strlen 遇到 \0 就截止。
15、sizeof(pchar3) 为 4 / 8,因为 pcahr3 是一个指针,指针的大小为 4 个字 (32位)、或者 8 个字节 (64 位)。
16、strlen(pcahr3) 为 4,由于 strlen 遇到 \0 就截止,计算 pchar3 有效字符的个数,所以这里为 4。
17、sizeof(ptr1) 为 4 / 8。因为 ptr1 也是一个指针。
注意:
常量区存储的数组是不可以更改的,而 char2 指向的数组存在于栈上,是可以更改的。
🙊operator new 与 operator delete 函数🙊
💖 介绍
new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete 全局函数来释放空间。注意这两个函数并不是重载而是库里面实现的全局函数。
💖 operator new 源代码分析
来看一下源代码:
/*
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);
}
operate new 源代码 _RAISE 表明如果 malloc 失败了就会抛出异常。
💖 operator delete 源代码分析
代码如下:
/*
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)
operate delete 进行了很多内存越界的检查,并最终 free。
💖 本质
operate new 和 operate delete 的本质是封装,operate new 的用法和 malloc 类似。
int main()
{
//失败以后抛异常
int* p1 = (int*)operate new(sizeof(int*));
//失败以后返回NULL
int* p2 = (int*)malloc(sizeof(int*));
if(p2 == nullptr)
{
perror("malloc fail");
exit(-1);
}
return 0;
}
而 operate new 和 malloc 的区别是,operate new 失败以后抛异常,而 malloc 失败以后返回 nullptr 。operate delete 和 free 区别不大,只是多了一些检查。
为什么会有 operate new 呢?
int main()
{
A* p1 = new A;
delete p1;
A* p2 = new A[10];
delete[] p6;
return 0;
}
1、由于 new 创建一个变量,需要先申请空间,再调用构造函数,而申请空间需要在堆上申请,这里 new 申请空间调用的不是 malloc 而是 operate new,因为 c++ 是面向对象的语言,面向对象使用抛异常来处理错误。所以使用 operate new 封装 malloc,封装失败就抛异常。
2、在这里 delete p1,是先调用析构函数再释放 p5 指向的空间。
3、而 new A[10] 申请空间调用的是 operate[ ] new ,operate[ ] new 再调用 operate new,operate new 再调用 malloc 函数。因为有 10 个 A 对象,这里调用了 10 次构造函数。
4、 delete[ ] p6 先调用 10 次析构函数,再 operate delete[ ] 整体释放 p6 指向的空间。
注意:
局部变量 p1、p2 定义在栈上,函数结束出了作用域栈帧销毁,栈上的变量 p1、p2 就销毁。但是 p1 和 p2 指向的空间在堆上,堆上的内容是需要我们去手动释放的。
再看下面一段代码:
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;
return 0;
}
此段代码创建的局部对象 st 不需要手动释放,因为局部对象定义的时候会调用默认构造函数,出了作用域会调用析构函数。再看第二种写法:
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* pst = new Stack;
delete pst;
return 0;
}
此段代码由于 pst 是内置类型,因为无论什么类型的指针都是内置类型,而与上面一种写法相比,由于 pst 是内置类型,在创建完成后不会调用析构函数,因此需要用 delete 进行释放。
如果将 delete 改成 free,相当于没有调用析构函数就将 pst 指向的空间释放了,_a 指向的空间没有被释放,这种情况就是内存泄漏。由于申请的内存都是由程序员自己管理,C / C++ 不会检查内存泄漏。
🙊 malloc / free 和 new / delete 的区别 🙊
malloc / free 和 new / delete 的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:
- malloc 和 free 是函数,new 和 delete 是操作符
- malloc 申请的空间不会初始化,new 可以初始化
- malloc 申请空间时,需要手动计算空间大小并传递,new 只需在其后跟上空间的类型即可,如果是多个对象,[ ] 中指定对象个数即可
- malloc 的返回值为void*, 在使用时必须强转,new 不需要,因为 new 后跟的是空间的类型
- malloc 申请空间失败时,返回的是 NULL,因此使用时必须判空,new 不需要,但是 new 需要捕获异常
- 申请自定义类型对象时,malloc / free 只会开辟空间,不会调用构造函数与析构函数,而 new 在申请空间后会调用构造函数完成对象的初化,delete 在释放空间前会调用析构函数完成空间中资源的清理
🙊 定位new表达式 🙊
定位 new 表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new ( place_address ) type 或者 new ( place_address ) type ( initializer-list )
place_address 必须是一个指针,initializer-list 是类型的初始化列表
使用场景:
当 malloc 或者 new 一块空间创建堆想的时候,想要对其初始化,由于成员私有,没办法直接进行初始化。那么如果想进行初始化就需要定位 new。构造函数不可以显示调用,而析构函数可以显式调用。
int main()
{
A aa;
A* p1 = (A*)malloc(sizeof(A));
if (p1 == nullptr)
{
perror("malloc fail");
}
// 对一块已有的空间初始化 -- 定位new
//new(p1)A;
new(p1)A(1);
p1->~A();
free(p1);
return 0;
}
当然,这种方法不如直接去 new 一个空间简洁。
int main()
{
A* p2 = new A;
delete p2;
return 0;
}
但是有一个场景使用定位 new 更合理,直接 new 是向操作系统堆区申请内存,为了提高行能,使用定位 new 去内存池申请。



855

被折叠的 条评论
为什么被折叠?



