C/C++内存管理
Posted 蚍蜉撼树谈何易
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++内存管理相关的知识,希望对你有一定的参考价值。
C++内存管理
C/C++内存分布
示例图:
栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
数据段:存放的是全局数据、与静态数据,全局数据又可分为已初始化的全局区(data)和未初始化的全局区(BSS);
代码段:可执行代码和只读常量(文字常量区);
内存映射段:文件映射、动态库、匿名映射
C语言内存开辟(堆上)的三大函数
面试题:malloc、calloc、realloc区别
三个函数的声明分别是:
void* realloc(void* ptr, unsigned newsize);
void* malloc(unsigned size);
void* calloc(size_t numElements, size_t sizeOfElement);
相同点:都在stdlib.h函数库内它们的返回值都是请求系统分配的地址,如果请求失败就返回NULL
**malloc用于申请一段新的地址,参数size为需要内存空间的长度,如:
char p;
p=(char)malloc(20);
calloc与malloc相似,参数sizeOfElement为申请地址的单位元素长度,numElements为元素个数,如:
char* p;
p=(char*)calloc(20,sizeof(char));
realloc是给一个已经分配了地址的指针重新分配空间,参数ptr为原有的空间地址,newsize是重新申请的地址长度
例如:
char* p;
p=(char*)malloc(sizeof(char)20);
p=(char)realloc(p,sizeof(char)*40);
注意,这里的空间长度都是以字节为单位。
C语言的标准内存分配函数:malloc,calloc,realloc,free等。
malloc与calloc的区别为1块与n块的区别:
malloc调用形式为(类型*)malloc(size):在内存的动态存储区中分配一块长度为“size”字节的连续区域,返回该区域的首地址。
calloc调用形式为(类型*)calloc(n,size):在内存的动态存储区中分配n块长度为“size”字节的连续区域,返回首地址。
realloc调用形式为(类型*)realloc(ptr,size):将ptr内存大小增大(减少)到size。类似于调整,可调大也可调小。
free的调用形式为free(voidptr):释放ptr所指向的一块内存空间。
探究realloc底层实现。
realloc()函数指的是在原有内存的基础上进行操作,对该内存的大小进行扩容或者是减容,底层实现:
只是从代码层面,没有从底层说
void* my_realloc(void* des, size_t size)
{
void* new_space = malloc(size);//1.申请空间
memcpy(new_space, des, size);//2.对原空间size大小个数据复制,此时size为新开辟的空间大小
free(des);//3释放原空间
des = NULL;
return new_space;//返回新开辟的空间
}
realloc(void*des,size_t size)会做四件事:
1.根据size申请一段新的空间
2.对原空间的内容拷贝原空间size个字节大小。(假如说缩容的话,则可能会造成数据丢失).
3.释放掉原空间(这点很关键),这块的realloc会有两种处理策略,一个是在原空间上进行操作(扩容或者减容)不去寻找新空间 ,第二种情况是在新空间中开辟一段空间。(因为这是操作系统做决定的,所以我们无法模拟出来)。
4.在开辟成功的基础上返回新开辟的空间首地址。
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 );
}
看一下这个需要free(p2)吗?答案是不需要的,因为根据上面那个realloc底层实现上来看,他会在封装realloc内部进行操作,free()掉原空间,所以此时入去再去free()的话就相当于对一块原空间free了两次(以上陈述建立在新空间中开辟)。
C++内存开辟与释放函数
内存开辟函数new与new[]操作符
new
new的底层实现:
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);
}
new什么时候会开辟失败?
内存不足的时候会开辟失败,此时它会进入上面的那个if(_callnewh(size)==0)这个判断语句,然后对内存中的资源进行回收,若回收到了的话分配,回收不到的话会抛出 bad_alloc类型异常
new的使用:
void test()
{
int* p = new int;
*p = 100;
cout << *p << endl;
}
int main()
{
test();
system("pause");
return 0;
}
new[]
底层相当于循环去调用malloc(),这个函数一般用来开辟数组
实现原理:
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申
请
2. 在申请的空间上执行N次构造函数
void* operator new[](size_t size=sizeof(type)*n);
void test()
{
int* p = new int[10];
for (int i = 0; i < 10; i++)
{
p[i] = i * 10;
}
for (int i = 0; i < 10; i++)
{
cout << p[i] << endl;
}
}
int main()
{
test();
system("pause");
return 0;
}
看底层
内存释放delete操作符
delete p;//释放p,释放单个变量
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
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 );//可以看到底层实现是用free做的
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
delete [] p ;//释放p所指向的数组空间
实现原理:
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
测试用例:
void test()
{
int* p = new int;
int* p1 = new int[10];
delete p;
delete[]p1;
}
int main()
{
test();
return 0;
}
c语言内存管理与C++内存管理的区别
1.C语言的三个在堆上的内存开辟函数的返回值需要进行强转(C++下),new的返回值不需要强转,因为在申请的时候编译器已经将其类型转好了
int *p=new int;//告诉编译器你要的是个int类型的数据。
2.C语言的开辟出来的空间的值除了calloc()函数之外,其与开辟出来空间的值皆为随机值。而C++的new可以初始化值。C++11中提出来的。
void test()
{
int* p =(int *) malloc(sizeof(int) * 10);
int* p1 = (int*)calloc(10, sizeof(int));
//底下这个语句给初始值就从0号下标开始依次往下赋值,没有初始化的元素赋值为0;
//若没有给初始值的话,比如 int *p2=new int[10];//此时里面的值全为随机值
int* p2 = new int[10]{ 1 };
free(p);
free(p1);
delete[]p2;
}
int main()
{
test();
return 0;
}
可以看到 malloc出现的数组的值全为随机值,calloc为0,new中因为我只给了1个初始化变量,所以除了第一个元素外,其余9个全为0.
3.C语言的内存开辟函数开辟出来的类对象无法自动调用类的构造函数。而new出来的对象可以。同时,free()自动调用类的析构函数,而delete可以,
class date
{
public:
date()
{
year = 1900;
month = 0;
day = 0;
cout << "构造函数调用" << endl;
}
~date()
{
cout << "析构函数调用" << endl;
}
private:
int year;
int month;
int day;
};
void test01()
{
date* m1 = (date*)malloc(sizeof(date) * 5);
cout << "free调用" << endl;
free(m1);
}
void test02()
{
date* m2 = new date[5];
cout << "delete调用" << endl;
delete[]m2;
}
int main()
{
cout << "malloc开辟" << endl;
test01();
cout << "new 开辟" << endl;
test02();
return 0;
}
4.如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.在自定义方法分析:首先看C语言的内存开辟函数,它只开辟了与对象同等大小的空间,但是它不是对象。C++提供的new开辟内存,干了两件事,第一件事开辟内存,第二件事调用构造函数,完成对类对象变量的初始化。它开辟出来的是一个对象。对应的delete会调用析构函数,若你有析构函数,则调用你自身的析构,若没有,则调用系统自动生成的析构。
在使用的时候必须对应使用
malloc()对应free() new对应delete new[]对象 delete[]
必须做到匹配使用
如果不匹配使用,轻则的话内存泄露,重则的话程序会崩溃。
1.我们试一下malloc开辟让delete去释放。
class student
{
public:
student()
{
cout << "构造调用" << endl;
age = 20;
char* name = new char[20];
}
~student()
{
if (name)
{
delete[]name;
}
cout << "析构调用" << endl;
}
private:
int age;
char* name;
};
void test()
{
student* m1 = (student*)malloc(sizeof(student));
if (m1 == NULL)
{
cout << "内存开辟失败,退出" << endl;
return;
}
delete m1;
}
int main()
{
test();
return 0;
}
2.用new开辟,free()释放
class student
{
public:
student()
{
cout << "构造调用" << endl;
age = 20;
char* name = new char[20];
}
~student()
{
if (name)
{
delete[]name;
}
cout << "析构调用" << endl;
}
private:
int age;
char* name;
};
void test()
{
student* m1 = new student;
free(m1);
}
int main()
{
test();
return 0;
}
3。new开辟,delete []释放
class student
{
public:
student()
{
cout << "构造调用" << endl;
age = 20;
char* name = new char[20];
}
~student()
{
if (name)
{
delete[]name;
}
cout << "析构调用" << endl;
}
private:
int age;
char* name;
};
void test()
{
student* m1 = new student;
delete[]m1;
}
int main()
{
test();
return 0;
}
4.new[]开辟,free释放,崩溃
5.new[]开辟,delete释放,崩溃
注:一定要匹配使用。不然可能要不出现内存泄露,要不然程序崩溃。
同一个类,自定义析构函数比不自定义析构函数多4字节?
前提:new[]开辟,开辟类数组
是因为
class student
{
public:
student()
{
cout << "构造调用" << endl;
age = 100;
}
~student()
{
cout << "析构调用" << endl;
}
private:
int age;
};
void test()
{
//student* m1 = new student;
//delete m1;
student* m2 = new student[10];
delete[]m2;
}
int main()
{
test();
return 0;
}
为了对比:
此时我们注释掉显式定义出的析构函数
此时我们再来看内存
重载new与delete操作符
class student
{
public:
student()
{
age = 12;
}
~student()
{
}
void* operator new(size_t size)
{
return malloc(size);
}
void operator delete(void* p)
{
if (p)
{
free(p);
}
}
private:
int age;
};
int main()
{
student* m1 = new student;
delete m1;
}
不过这样重载感觉很蠢,我们一般会在固定需求时使用。
malloc()
malloc(size)为C语言开辟内存函数,它一般开辟size个大小,那么它真的就占这么大吗?当然不是,因为你要管理该空间,管理是不是要存储这部分的起始地址,和结束地址,所以在C/C++中有一个结构体,专门管理这部分空间。
#define nNoMansLandSize 4
typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
struct _CrtMemBlockHeader *pBlockHeaderPrev;
char *szFileName; // File name
int nLine; // Line number
size_t nDataSize; // Size of user block
int nBlockUse; // Type of block
long lRequest; // Allocation number
// Buffer just before (lower than) the user's memory:
unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;
这是管理malloc的结构体的,共占32个字节。
// Pointer to the block allocated just before this one:
struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
struct _CrtMemBlockHeader *pBlockHeaderPrev;
可以看到开辟的空间是用双向链表组织起来的。
定位new
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type或者new (place_address) type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义
类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化
class Test
{
public:
Test()
: _data(0)
{
cout<<"Test():"<<this<<endl;
}
~Test()
{
cout<<"~Test():"<<this<<endl;
}
private:
int _data;
};
void Test()
{
// pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
Test* pt = (Test*)malloc(sizeof(Test));
new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
}
new(pt) Test;没有去开辟空间,因为空间已经malloc出来了,只是让其去调用test的构造函数而已。
以上是关于C/C++内存管理的主要内容,如果未能解决你的问题,请参考以下文章