c/c++内存分布
虚拟地址空间分布
- 栈又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共 享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
- 堆用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段–存储全局数据和静态数据。
- 代码段–可执行的代码/只读常量。
为什么操作系统要划分这些区段
答:为了找数据,存数据方便
以下的代码分别在虚拟地址空间中的哪个段
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"; //char数组在栈,里面存储的abcd字符串常量在代码段
char* pChar3 = "abcd"; //pChar指针在栈上,里面的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);
}
sizeof与strlen的区别
- sizeof()是运算符,strlen()是库函数
- sizeof()在编译时计算好了,strlen()在运行时计算
- sizeof()计算出对象使用的最大字节数,strlen()计算字符串的实际长度
- sizeof()的参数类型多样化(数组,指针,对象,函数都可以),strlen()的参数必须是字符型指针(传入数组时自动退化为指针)
c语言动态开辟空间
动态申请空间的方式
- malloc
void * malloc ( size_t size );
- calloc
void * calloc ( size_t num, size_t size );
- realloc
void * realloc ( void * ptr, size_t size );
相同点:
- 都是从堆上开辟空间
- 返回值类型都是
void*
- 申请空间失败,返回的都是NULL
- 申请的空间都需要手动释放
- 在接收返回值时都需要进行强制类型转换
不同点
malloc:
- 参数:字节数,
- 不会对申请的空间进行初始化,
- 在接收返回值时必须强制类型转换
- 如果申请空间失败,返回值NULL,使用前必须判断是否为空
calloc:
- 参数列表不同:
void * calloc ( size_t num, size_t size );
,第一个为元素个数,第二个为单个元素大小 - 函数功能差异:
calloc
会将申请的空间初始化为0
realloc
- 参数列表:
void * realloc ( void * ptr, size_t size );
- 函数功能:将
ptr
所指向的空间调整到size
字节,如果ptr
为空----->函数行为与malloc相同 - ptr非空,判断是缩小还是增大,缩小的话,对原空间进行调整,最后返回该空间的首地址,扩大的话,有两种情况:1. 扩大一点,2.扩大很多
1.如果当前空间后面有足够的空间能够支持size字节的话,直接扩容
2.如果没有足够的空间,realloc做四件事情,第一:申请新空间,第二:拷贝元素,第三:释放旧空间,第四:返回新空间的地址
动态释放方式
手动free
malloc
int* p =(int*)malloc(10*sizeof(int));
40字节-----堆上,只要把空间分配给一个应用程序,那么其他应用程序就用不了。系统必须对malloc
申请的空间进行管理。
一般malloc申请空间时,比如申请40个字节,除了申请的40字节空间,他还会在前面加一个结构体来管理40字节的空间,在后面加4个字节的空间用来防止越界
C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出 了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
new申请内置类型
int main()
{
//new 申请单个类型元素的空间----默认情况下new出的空间在堆上
int *p1 = new int;
int *p2 = new int(10);
int *p3 = new int[10];
int *p4 = new int[10]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
delete p1;
delete p2;
delete[] p3;
delete[] p4;
return 0;
}
new
和delete
不是函数,c++提供的新的操作符new/new[]
只需在其后跟空间的类型,不需要传递字节数new
后跟的就是空间的类型,不需要强制类型转换new/new[]
可以进行初始化new
的结果不需要判空
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和 delete[]
如果没有匹配
如果申请的是内置类型的空间,没有匹配的话,不会产生任何后果
void Test()
{
int *p1 = (int *)malloc(sizeof(int)* 4);
int *p2 = (int *)malloc(sizeof(int)* 4);
delete p1;
delete p2;
int *p3 = new int;
int *p4 = new int;
free(p3);
delete[]p4;
int *p5 = new int[10];
int *p6 = new int[10];
free(p5);
delete p6;
}
new申请自定义类型数据
未进行匹配使用
//自定义类型
class Test
{
public:
Test()
{
_data = 10;
cout << "Test():" << this <<endl;
}
~Test()
{
cout << "~Test()" << this << endl;
}
private:
int _data;
};
void Test2()
{
Test *p1 = (Test *)malloc(sizeof(Test)* 4);
//Test *p2 = (Test *)malloc(sizeof(Test)* 4);
delete p1;
//delete[] p2;
Test *p3 = new Test;
Test *p4 = new Test;
free(p3);
delete[]p4;
Test *p5 = new Test[10];
Test *p6 = new Test[10];
free(p5);
delete p6;
}
- 对于自定义类型,只要设计到
[]
不匹配,肯定崩溃 new
会调用构造函数,而free
不会调用析构函数----对象中的资源不会被销毁—内存泄露malloc
申请空间时不会调用构造函数—申请的是与对象大小相同的一块内存空间,不能将该块内存空间看成是一个对象delete
需要调用析构函数
operator new与operator delete函数
new是先调用构造函数,还是先申请空间?
第一件事情:申请内存空间,第二才是调用构造函数,完成对象的初始化
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);
}
循环调用malloc
来不断申请空间,直到申请成功,
- malloc申请----->成功----->直接返回
- malloc申请------>失败(内存空间不足)------->检测是否提供空间不足的应对措施,提供:继续申请,未提供:抛异常
/* operator delete: 该函数最终是通过free来释放空间的 */
void operator delete(void *pUserData)
{
//_CrtMemBlockHeader 这个就是malloc申请内存时前面的哪个结构体
_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;
}
delete p;
- 调用析构函数释放对象中的资源-----对象中的资源
- 调用
void operator delete(void *p)
释放对象本身的空间
总结
operator new 实际也是通过malloc来申请空间,如果malloc申请空间 成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异 常。operator delete 最终是通过free来释放空间的
void* operator new(size_t size,const char* file,const char* funname,size_t line)
{
cout << file << "-" << funname << "-" << line << "-" << size << endl;
return malloc(size);
}
void operator delete(void *p, const char* file, const char* funname, size_t line)
{
cout << file << "-" << funname << "-" << line << "-" << endl;
free(p);
}
#define new new(__FILE__, __FUNCDNAME__, __LINE__)
int main()
{
int *p = new int;
delete p;
system("pause");
return 0;
}
new和delete的实现原理
内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和 释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常, malloc会返回NULL
自定义类型
- new的原理
-
- 调用operator new函数申请空间
-
- 在申请的空间上执行构造函数,完成对象的构造
- delete的原理
-
- 在空间上执行析构函数,完成对象中资源的清理工作
-
- 调用operator delete函数释放对象的空间
- new T[N]的原理
-
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申 请
-
- 在申请的空间上执行N次构造函数
- delete[]的原理
-
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
-
- 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
内存碎片化
如果每回一小块一小块申请内存,就会造成内存的浪费。所以c++有一个现成的内存池就是空间配置器
以下代码对链表的节点ListNode通过重载类专属 operator new/ operator delete,实现链表节 点使用内存池申请和释放内存,提高效率。
struct ListNode {
ListNode* _next;
ListNode* _prev;
int _data;
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List {
public:
List()
{
_head = new ListNode;
_head->_next = _head;
_head->_prev = _head;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
定位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;
};
int main()
{
Test* pt = (Test*)malloc(sizeof(Test));
new(pt)Test;
//delete pt;
pt->~Test();
system("pause");
return 0;
}