目录
1.C/C++的内存分布
2.C语言中动态内存管理方式:malloc、calloc、realloc、free
3.C++内存管理方式
3.1 new/delete操作内置类型
3.2 new 和 delete操作自定义类型
4.operator new 与 operator delete 函数(重要点)
4.1 operator new 与 operator delete函数(重点)
4.2 重载 operator new 与operator delete(了解)
5.new 和 delete 的实现原理
5.1 内置类型
5.2 自定义类型
6. 定位 new 表达式(placement-new)(了解)
7.常见面试题
7.1 malloc / free 和 new / delete 的区别
7.2 内存泄漏
7.2.1什么式内存泄漏,内存泄漏的危害
7.2.2 内存泄漏的危害
7.2.3 如何检测内存泄漏(了解)
7.2.4 如何避免内存泄漏
我们先来看一下下面的代码和相关问题
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. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?__C__ // 全局变量,全局变量和静态数据存放在 数据段(静态区)
staticGlobalVar在哪里?__C__ // 静态数据,全局变量和静态数据段存放在 数据段(静态区)
staticVar在哪里?__C__ // 静态数据,全局变量和静态数据段存放在 数据段(静态区)
localVar在哪里?__A__ //局部变量, 局部变量和函数存放在栈
num1 在哪里?__A__ //局部变量, 局部变量和函数存放在栈
char2在哪里?__A__ //局部变量, 局部变量和函数存放在栈
*char2在哪里?__A_ //局部变量, 局部变量和函数存放在栈
pChar3在哪里?__A__ //pChar3是一个指针变量,存储'abcd'的地址,属于局部变量, 局部变量和函数存放在栈
*pChar3在哪里?__D__ //pChar3是一个指针变量,存储'abcd'的地址,解引用后找到'abcd','abcd\0'存储在常量区。
ptr1在哪里?__A__ //ptr1是一个指针变量,存储malloc的地址,属于局部变量, 局部变量和函数存放在栈
*ptr1在哪里?__B__ //ptr1是一个指针变量,存储malloc的地址,解引用后拿到malloc的内容,该内容存放在堆区。
2. 填空题:
sizeof(num1) = __40__; //sizeof(数组名),求该数组的大小,4 * 10 = 40
sizeof(char2) = __5__; //sizeof(数组名),求该数组的大小,"abcd" = "abcd\0" 一共5个元素,1 * 5 = 5
strlen(char2) = __4__; //strlen()是库函数,求字符串长度,"abcd"一共4个元素。
sizeof(pChar3) = _4/8___;//pChar3是一个指针变量,指针变量的大小受操作系统影响,4/8
strlen(pChar3) = __4__; //strlen()是库函数,求字符串长度,"abcd"一共4个元素。
sizeof(ptr1) = __4/8__; //ptr1是一个指针变量,指针变量的大小受操作系统影响,4/8
3. sizeof 和 strlen 区别?
sizeof()是操作符,求取变量的大小,
strlen()是函数,求的是字符串的长度,需要注意字符串一般末尾有'\0'作为结束标识符。
【说明】
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)吗?
/*
不需要,realloc扩容方式分为两种:
原地扩容,地址不改变。
新位置扩容,copy原位置的内容,给到新位置,返回新位置的地址。
*/
free(p3 );
}
面试题:
1.malloc/calloc/realloc的区别?
malloc申请的空间没有初始化,直接返回起始地址。
calloc申请的空间,会把空间初始化为0,然后返回起始地址。
realloc是对已经开辟好的空间进行扩容,扩容方式分为两种:
原地扩容,地址不改变。
新位置扩容,copy原位置的内容,给到新位置,返回新位置的地址。
2.malloc的实现原理?
【CTF】GLibc堆利用入门-机制介绍_哔哩哔哩_bilibili
C语言内存管理方式在C++中可以继续使用,但是有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
#include
using namespace std;
int main()
{
//动态申请一个int类型的空间
//C
int* p1 = (int*)malloc(sizeof(int));
free(p1);
//CPP
int* p2 = new int;
delete p2;
//动态申请10个int类型的空间
//C
int* p3 = (int*)malloc(sizeof(int) * 10);
free(p3);
//CPP
int* p4 = new int[10];
delete[] p4;
//动态申请一个int类型的空间并初始化为10
//CPP
int* p5 = new int(10);
cout << "p5;" << *p5 << endl;
delete p5;
//动态申请一个int类型的数组空间并初始化为{1,2,3,0}
//CPP
int* p6 = new int[10] {1,2,3,0};
for (int i = 0; i < 10; i++)
{
cout << "p6;" << *(p6+i) << endl;
}
delete[] p6;
return 0;
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用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()
{
//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不会。
【面试题】
1.C语言malloc\free 与C++new、delete 的区别?
动态申请内置类型的数据:new / malloc 除了用法上面,其他没有什么区别。
动态申请自定义类型的数据:new / malloc 除了用法上面,new会调用构造函数初始化,delete会调用析构函数清理。
new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new在底层调用了operator new 全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
/*
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来释放空间的。
注意:一般情况下不需要对operator new 和 operator delete进行重载,除非在申请和释放空间的时候有某些特殊的需求。比如:在使用new和delete申请空间和释放空间时,打印一些日志信息,可以帮助用户来检测时候存在内存泄漏。
// 重载operator delete,在申请空间时:打印在哪个文件、哪个函数、第多少行,申请了多少个
字节
void* operator new(size_t size, const char* fileName, const char* funcName,
size_t lineNo)
{
void* p = ::operator new(size);
cout << fileName << "-" << funcName << "-" << lineNo << "-" << p << "-"
<< size << endl;
return p;
}
// 重载operator delete,在释放空间时:打印再那个文件、哪个函数、第多少行释放
void operator delete(void* p, const char* fileName, const char* funcName,
size_t lineNo)
{
cout << fileName << "-" << funcName << "-" << lineNo << "-" << p <<
endl;
::operator delete(p);
}
int main()
{
// 对重载的operator new 和 operator delete进行调用
int* p = new(__FILE__, __FUNCTION__, __LINE__) int;
operator delete(p, __FILE__, __FUNCTION__, __LINE__);
return 0;
}
// 上述调用显然太麻烦了,可以使用宏对调用进行简化
// 只有在Debug方式下,才调用用户重载的 operator new 和 operator delete
#ifdef _DEBUG
#define new new(__FILE__, __FUNCTION__, __LINE__)
#define delete(p) operator delete(p, __FILE__, __FUNCTION__, __LINE__)
#endif
int main()
{
int* p = new int;
delete(p);
return 0;
}
如果申请的是内置类型的空间,new 和 malloc,delete 和free 基本相似,不同的地方是:new / delete 申请和释放的是首个元素的空间,new[] 和 delete[] 申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL;
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new(place_address)type 或者 new(place_address)type(initializer-list)
place_address 必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位 new 表达式在实际中一般是配和内存池使用,因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用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;
}
什么式内存泄偶?
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况,内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计失误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害?
长期运行的程序出现内存泄漏影响很大,如操作系统,后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏:
堆内存指的是程序执行中依据要分配通过malloc / calloc / realloc / new 等从堆中分配的一块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比如套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
在vs下可以使用windows操作系统提供的 _CrtDumpMemoryLeaks() 函数进行简单检测,该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。
int main()
{
int* p = new int[10];
// 将该函数放在main函数之后,每次程序退出的时候就会检测是否存在内存泄漏
_CrtDumpMemoryLeaks();
return 0;
}
// 程序退出后,在输出窗口中可以检测到泄漏了多少字节,但是没有具体的位置
Detected memory leaks!
Dumping objects ->
{79} normal block at 0x00EC5FB8, 40 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.
因此写代码时一定要小心,尤其是动态内存操作时,一定要记着释放。但有些情况下总是防不胜防,简单的可以采用上述方式快速定位下。如果工程比较大,内存泄漏位置比较多,不太好查时一般都是借助第三方内存泄漏检测工具处理的。
总结一下:内存泄漏非常常见,解决方案分为两种:
1、事前预防型。如智能指针等。
2、事后查错型。如泄漏检测工具。