目录
一、C/C++中程序内存分布
关于存储的问题
二、C语言中动态内存管理方式
malloc/calloc/realloc和free
malloc
free
calloc
realloc
三、C++内存管理方式
new/delete操作内置类型
小问题:
对于自定义类型
关于调用自定义类型的实例
四、operator new与operator delete函数
对于开空间失败而言,C与C++不同之处
operator new
operator new与operator delete函数的底层实现(了解)
operator new与operator delete的类专属重载(了解)
内存池的概念:
五、new和delete的实现原理
内置类型
自定义类型
new的原理
delete的原理
new T[N]的原理
delete[]的原理
六、定位new表达式(placement-new) (了解)
使用格式:
使用场景:
七、常见面试题
malloc/free和new/delete的区别
八、内存泄漏
什么是内存泄漏
内存泄漏的危害
代码体现
内存泄漏分类(了解)
如何避免内存泄漏
九、如何一次在堆上申请4G的内存
一、C/C++中程序内存分布
ps: y以下内存是以32位程序下,linux系统讨论
这块叫进程虚拟地址空间(大约4G),每个程序都有这个空间,哪块要存数据,就要跟物理内存(电脑的实际内存)进行建立映射。平时在程序里看到的地址都是这块空间的地址,程序用那块,他就会建立那块的映射
栈,静态区,常量区,这块的空间的生命周期都是自动控制的
堆:程序运行过程中按需求,申请和释放空间,比如我们实现链表,数组栈等等,都是在堆开空间。堆这块空间的生命周期是手动控制的。
ps:栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放,堆无法静态分配,只能动态分配;
关于存储的问题
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
int num2[] = { 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
.
代码段(常量区)
(1)globalVar
在哪里?
____ (2)
staticGlobalVar
在哪里?
____
(3)staticVar
在哪里?
____ (4)
localVar
在哪里?
____
(5)num1
在哪里?
____
答案:C,C,C,A,A;
解析:globalVar是全局变量,在静态区;staticGlobalVar,staticVar都有static修饰,在静态区;localVar,num1 都是局部变量,在栈上。
(1)char2
在哪里?
____(2)
*
char2
在哪里?
___
(3)pChar3
在哪里?
____ (4)
*
pChar3
在哪里?
____
(5)ptr1
在哪里?
____ (6)
*
ptr1
在哪里?
____
答案:A,A,A,D,A,B
解析:
在栈帧上定义了pchar3,ptr1,他俩是指针, pchar3存的是“abcd”这块常量的地址。ptr1是在堆上开了块空间,存的是这块空间首元素的地址。char2在栈开了块空间,因为它是个数组,然后把“abcd”这块常量,拷贝到了空间中去。所以解引用char2还是在栈上,解引用pchar3就在常量区,解引用ptr1就在堆上。
ps:指针在全局定义的它就在静态区,在局部定义的就在栈上,看在哪定义。局部变量与类型是没有关系的。
ps: y以下内存是以32位程序下,linux系统讨论
这块叫进程虚拟地址空间(大约4G),每个程序都有这个空间,哪块要存数据,就要跟物理内存(电脑的实际内存)进行建立映射。平时在程序里看到的地址都是这块空间的地址,程序用那块,他就会建立那块的映射
栈,静态区,常量区,这块的空间的生命周期都是自动控制的
堆:程序运行过程中按需求,申请和释放空间,比如我们实现链表,数组栈等等,都是在堆开空间。堆这块空间的生命周期是手动控制的。
ps:栈可以通过函数_alloca进行动态分配,不过注意,所分配空间不能通过free或delete进行释放,堆无法静态分配,只能动态分配;
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
int num2[] = { 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 . 代码段(常量区) (1)globalVar 在哪里? ____ (2) staticGlobalVar 在哪里? ____ (3)staticVar 在哪里? ____ (4) localVar 在哪里? ____ (5)num1 在哪里? ____ 答案:C,C,C,A,A; 解析:globalVar是全局变量,在静态区;staticGlobalVar,staticVar都有static修饰,在静态区;localVar,num1 都是局部变量,在栈上。 (1)char2 在哪里? ____(2) * char2 在哪里? ___ (3)pChar3 在哪里? ____ (4) * pChar3 在哪里? ____ (5)ptr1 在哪里? ____ (6) * ptr1 在哪里? ____答案:A,A,A,D,A,B
解析:
在栈帧上定义了pchar3,ptr1,他俩是指针, pchar3存的是“abcd”这块常量的地址。ptr1是在堆上开了块空间,存的是这块空间首元素的地址。char2在栈开了块空间,因为它是个数组,然后把“abcd”这块常量,拷贝到了空间中去。所以解引用char2还是在栈上,解引用pchar3就在常量区,解引用ptr1就在堆上。
ps:指针在全局定义的它就在静态区,在局部定义的就在栈上,看在哪定义。局部变量与类型是没有关系的。
2.填空题 (1)sizeof ( num1 ) = ____ ;(2) sizeof ( num2 ) = ____ ; (3)sizeof ( char2 ) = ____ ; (4) strlen ( char2 ) = ____ ; (5)sizeof ( pChar3 ) = ____ ;(6) strlen ( pChar3 ) = ____ ; (7)sizeof ( ptr1 ) = ____ ;答案:40,16,5,4,4/8,4,4/8
解析:num1 int类型开了10个空间,一共40个字节; num2 int类型开了4个空间;
char2,"abcd ", ,占空间但是不算长度,sizeof计算空间,strlen计算长度;
32位下指针4字节,64位下,指针8个字节
二、C语言中动态内存管理方式
malloc/calloc/realloc和free
malloc
函数原型:void*
malloc
(
size_t
size
);
这个函数向内存申请一块
连续可用
的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
free
函数原型:void
free
(
void*
ptr
);
free
函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。 如果参数 ptr 是NULL指针,则函数什么事都不做。
calloc
函数原型:void*
calloc
(
size_t
num
,
size_t
size
);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
realloc
函数原型:void*
realloc
(
void*
ptr
,
size_t
size
);
ptr 是要调整的内存地址 size 调整之后新大小
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存, 我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
三、C++内存管理方式
C
语言内存管理方式在
C++
中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此
C++
又提出了自己的内存管理方式:通过
new
和
delete
操作符进行动态内存管理
new的用法:
如果开辟成功,则返回一个指向开辟好空间的指针。 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
函数原型:void free ( void* ptr ); free 函数用来释放动态开辟的内存。如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。 如果参数 ptr 是NULL指针,则函数什么事都不做。
calloc
函数原型:void*
calloc
(
size_t
num
,
size_t
size
);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
realloc
函数原型:void*
realloc
(
void*
ptr
,
size_t
size
);
ptr 是要调整的内存地址 size 调整之后新大小
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存, 我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
三、C++内存管理方式
C
语言内存管理方式在
C++
中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此
C++
又提出了自己的内存管理方式:通过
new
和
delete
操作符进行动态内存管理
new的用法:
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
函数原型:void* realloc ( void* ptr , size_t size );ptr 是要调整的内存地址 size 调整之后新大小
realloc函数的出现让动态内存管理更加灵活。 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存, 我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
三、C++内存管理方式 C 语言内存管理方式在 C++ 中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此 C++ 又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理 new的用法:
delete的用法:
另外两种相匹配对应的:
new/delete操作内置类型
动态申请int和5个int数组,
int main()
{
//动态申请int和5个int数组 函数和关键字
//总结 malloc/free 和 new/delete 对于内置类型来讲没有本质区别,只有用法上的区别
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)malloc(sizeof(int) * 5);
int* p3 = new int;
int* p4 = new int[5]; //动态申请5个int空间
free(p1);
free(p2);
delete p3;
delete[]p4;
p1 = nullptr;
p2 = nullptr;
p3 = nullptr;
p4 = nullptr;
}
malloc/free 和 new/delete 对于内置类型来讲没有本质区别,只有用法上的区别
但要注意的是:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[ ]和 delete[ ]
PS:C++98不支持初始化new的数组,但是C++11支持用{}初始化
int main() { //C++98不支持初始化new的数组,C++11支持用{}初始化 int* p = new int[5]{ 1,2,3,4,5 }; }
小问题:
这两个有区别吗?
int main()
{
int* p1 = new int[5];
int* p2 = new int(5);
}
答案是有的,第一个p1是动态申请5个int空间,第二个p2是动态申请一个int空间,并将这块空间初始化成5。
对于自定义类型
这两个有区别吗?
int main()
{
int* p1 = new int[5];
int* p2 = new int(5);
}
答案是有的,第一个p1是动态申请5个int空间,第二个p2是动态申请一个int空间,并将这块空间初始化成5。
对于这样一个类A
class A
{
public:
A(int a=0):_a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
如果我们想要去申请自定义类型的空间时,我们可以使用malloc或者new,那么他俩有啥区别呢?
同样是动态申请单个A对象,和5个A对象数组
int main() { //动态申请单个A对象,和5个A对象数组 A* p1 = (A*)malloc(sizeof(A)); A* p2 = (A*)malloc(sizeof(A) * 5); //在堆上申请空间+调用构造函数初始化对象 A* p3 = new A; A* p4 = new A[5]; }通过执行结果监视我们发现malloc出来的啥都没干,new出来的调用了构造函数去初始化了对象,一共调用了6次构造函数。
同样对于free与delete
int main() { //动态申请单个A对象,和5个A对象数组 A* p1 = (A*)malloc(sizeof(A)); A* p2 = (A*)malloc(sizeof(A) * 5); //在堆上申请空间+调用构造函数初始化对象 A* p3 = new A; A* p4 = new A[5]; free(p1); free(p2); delete p3; delete[]p4; }free就是释放掉了空间,delete除了释放空间,而且还调用了析构函数
还需要注意的是:malloc和free ,new和delete 要匹配使用,不要混合使用,否则可能会崩溃。
同样new 和delete 匹配 ,new[ ] 和delete [ ] 匹配
eg:
如果类中没有默认构造函数,申请空间时也会报错。
解决方法:
1.0 提供默认构造函数
2.0 单个用括号初始化,数组用{ }初始化
A* p3 = new A(100); A* p4 = new A[5]{1,2,3,4,5};
关于调用自定义类型的实例
对于这样一个栈类
class Stack
{
public:
Stack(int capacity =4)
:_top(0)
,_capacity(capacity)
{
_a = new int[capacity];
}
~Stack()
{
delete[] _a;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1; //st1在栈上
//搞一个自己可以控制生明周期的
Stack* pst2 = new Stack;//开空间+构造函数初始化
//对象的指针
delete pst2; //析构函数(清理对象中的资源)+释放空间
return 0;
}
pst2指向动态开辟的空间,这个空间上有三个数据,一个指针,两个整形,紧接着Stack的构造函数又要开空间,开capacity个空间,第一个空间是给这个栈对象开空间,第二个空间是构造函数给这个栈对象里面的资源进行开空间。
delete pst2 第一步调用它的析构函数,清理调给_a开的空间,第二步释放掉栈对象本身的空间
总结下:new 在堆上申请空间+调用构造函数初始化对象
delete 先调用指针类型的析构函数+给堆上释放空间
C++提出new和delete,主要是解决两个问题
1.0自定义类型对象自动申请的时候,初始化和清理的问题。new和delete会调用构造函数和析构函数
2.0 new失败了以后要求抛异常,这样才符合面向对象语言的出错处理机制。
ps:delete和free一般不会失败,如果失败了,都是释放空间上存在越界或者释放指针位置不对。
四、operator new与operator delete函数
对于开空间失败而言,C与C++不同之处
面向对象的语言,处理错误的方式一般是抛异常,C++中也要求出错抛异常--try catch
面向过程的语言,处理错误的方式是返回值+代码解决 (C语言)
C语言失败是将指针置空,C++是抛异常 【异常必须被捕获,不捕获就会报错。C++用tyr与catch实现该操作】
C++捕获异常操作:结合try与catch使用,会输出 bad allocation
int main()
{
char* p1 = (char*)malloc(0x7fffffff); //2g
if (p1 == nullptr)
{
printf("malloc failn");
}
try
{
char* p2 = new char[0x7fffffff];
}
catch (const exception&e)
{
cout << e.what() << endl;
}
return 0;
}
面向对象的语言,处理错误的方式一般是抛异常,C++中也要求出错抛异常--try catch
面向过程的语言,处理错误的方式是返回值+代码解决 (C语言)
C语言失败是将指针置空,C++是抛异常 【异常必须被捕获,不捕获就会报错。C++用tyr与catch实现该操作】
C++捕获异常操作:结合try与catch使用,会输出 bad allocation
int main() { char* p1 = (char*)malloc(0x7fffffff); //2g if (p1 == nullptr) { printf("malloc failn"); } try { char* p2 = new char[0x7fffffff]; } catch (const exception&e) { cout << e.what() << endl; } return 0; }
operator new
针对这个栈类实例
class Stack
{
public:
Stack(int capacity =4)
:_top(0)
,_capacity(capacity)
{
_a = new int[capacity];
}
~Stack()
{
delete[] _a;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack st1; //st1在栈上
//搞一个自己可以控制生明周期的
Stack* pst2 = new Stack;//开空间+构造函数初始化
//对象的指针
delete pst2; //析构函数(清理对象中的资源)+释放空间
return 0;
}
我们通过反汇编观察 new和delete的底层
通过反汇编我们发现给pst2开空间底层是调用了operator new这个函数
operator new 又是什么呢?
实际上它就是一个库函数,并不是new的重载,我们自己可以用它去开空间
它是不会去调用构造函数的
实际上它与malloc的用法是完全一样
这个函数并不是直接提供给用户用的,实际上它是要对malloc的封装。operator new中调用malloc申请内存,失败以后,改为抛异常处理错误,这样才符合C++面向对象语言处理错误的方式
如果说没有operator new ,在调用new(因为new是操作符)的时候,就会被转换成指令 call malloc +call 构造函数,调用构造函数一般而言不会失败,调用malloc假如失败,就返回0,那么new也是返回0,但是这样就不符合C++处理错误的方式,所以C++就增加了operator new ,专门给new 用。
可以简单理解为:
new=operator new +构造函数
operator new = malloc+抛异常
operator new与operator delete函数的底层实现(了解)
new
和
delete
是用户进行
动态内存申请和释放的操作符
,
operator new
和
operator delete
是系统提供的
全局函数
,
new
在底层调用
operator new
全局函数来申请空间,
delete
在底层通
operator delete
全局函数来释放空间。
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);
}
void operator delete(void *pUserData) {
_CrtMemBlockHeader * pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK);
__TRY
pHead = pHdr(pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg( pUserData, pHead->nBlockUse );
__FINALLY
_munlock(_HEAP_LOCK);
__END_TRY_FINALLY
return; }
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
通过上述两个全局函数的实现知道,
operator new
实际也是通过
malloc
来申请空间
,如果
malloc
申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过
free
来释放空间的。
operator new与operator delete的类专属重载(了解)
针对这个list讲解
struct ListNode
{
ListNode* _next;
ListNode* _prev;
int _data;
ListNode(int val)
:_next(nullptr)
, _prev(nullptr)
, _data(val)
{}
void* operator new(size_t n) //这时候我就不在malloc申请
{
void* p = nullptr;
p = allocator().allocate(1); //stl中的内存池--空间配置器
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
};
class List
{
public:
List()
{
_head = new ListNode(-1);
_head->_next = _head;
_head->_prev = _head;
}
void PushBack(int val)
{
ListNode* newnode = new ListNode(val); //这调用的是全局的operator new
ListNode* tail = _head->_prev;
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
~List()
{
ListNode* cur = _head->_next;
while (cur != _head)
{
ListNode* next = cur->_next;
delete cur;
cur = next;
}
delete _head;
_head = nullptr;
}
private:
ListNode* _head;
};
int main()
{
List l;
l.PushBack(1);
l.PushBack(2);
l.PushBack(3);
l.PushBack(4);
l.PushBack(5);
return 0;
}
给了一个list,使用的时候会用很多的节点,都是小块小块的内存,频繁的去找系统要内存效率可能会比较低,能不能让它动态申请内存的时候不要去找系统要,而是找内存池去申请内存,进而提高效率呢?
内存池的概念:
内存池是啥?
比如说有一家人在山上住着,山顶上没有水,只有山下有一条河,想用水的时候该怎么办呢?
第一种方式就是:用的时候才去山下打水,比如要喝水,去山下喝了水以后,又回到了山上,每次用水的时候都下山去打水回来,用一点拿一点。
[平时我们使用就类似第一种方式:操作系统的堆就像这条小河,链表要插入数据就需要一个节点,就需要去找一下堆,频繁去找堆,效率就是低的。]
第二种方法就是,在山上建一个水池,提前在这个水池里储备好水,用的时候,让水池里放点水出来,这样就比第一种高效。
[内存池就类似第二种方式,不要去频繁的找堆,提前把内存储备起来,找堆,一次性申请很多,把它们放到内存池中,效率就高很多了,这种技术就叫做池化技术] .
这两个就是我们在Listnode里实现的专属operator new 与operator delete,这样的话使用new就不会去调用全局的operator new(系统库里面的),而是调用我们自己写好的,实现链表节点使用内存池申请和释放内存,提高效率
void* operator new(size_t n) //这时候我就不在malloc申请
{
void* p = nullptr;
p = allocator().allocate(1); //stl中的内存池--空间配置器
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
通过运行结果显示,确实调用了专属的operator new 与operator delete
五、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来释放空间
- 调用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来释放空间
- 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请 在申请的空间上执行N次构造函数
delete[]的原理
在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间
实际上newT[N] 与delete [ ] 就是对 operator new与operator delete的封装
六、定位new表达式(placement-new) (了解)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
new (place_address) type
或者
new (place_address) type(initializer-list)
place_address
必须是一个指针,
initializer-list
是类型的初始化列表
使用场景:
定位
new
表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new
的定义表达式进行显示调构造函数进行初始化。
eg:对这样的一个类
class Test
{
public:
Test(int data=0)
: _data(data)
{
cout << "Test():" << this << endl;
}
~Test()
{
cout << "~Test():" << this << endl;
}
private:
int _data;
};
int main()
{
Test* p = (Test*)malloc(sizeof(Test));
new(p)Test(1);
//new(p)Test;
Test* p2 = new Test(2);
delete p2;
//等价于
Test* p3 = (Test*)operator new(sizeof(Test));
new(p3)Test(3);
p3->~Test();
operator delete(p3);
}
malloc出来的不会调用构造函数,用定位new去初始化malloc出来的p;
ps: p
现在指向的只不过是与
Test
对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
这种是初始化成自己想要的值。
同理对operator new也一样可以使用
七、常见面试题
malloc/free和new/delete的区别
malloc/free
和
new/delete
的
共同点
是:都是从堆上申请空间,并且需要用户手动释放。
不同的
地方是:
class Test
{
public:
Test(int data=0)
: _data(data)
{
cout << "Test():" << this << endl;
}
~Test()
{
cout << "~Test():" << this << endl;
}
private:
int _data;
};
int main()
{
Test* p = (Test*)malloc(sizeof(Test));
new(p)Test(1);
//new(p)Test;
Test* p2 = new Test(2);
delete p2;
//等价于
Test* p3 = (Test*)operator new(sizeof(Test));
new(p3)Test(3);
p3->~Test();
operator delete(p3);
}
malloc出来的不会调用构造函数,用定位new去初始化malloc出来的p;
ps: p 现在指向的只不过是与 Test 对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行这种是初始化成自己想要的值。
同理对operator new也一样可以使用
七、常见面试题
malloc/free和new/delete的区别
malloc/free
和
new/delete
的
共同点
是:都是从堆上申请空间,并且需要用户手动释放。
不同的
地方是:
1. malloc和free是函数,new和delete是操作符 2. malloc申请的空间不会初始化,new可以初始化 3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可 4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型 5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常 6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间 后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
八、内存泄漏
什么是内存泄漏
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
通俗一点来讲就是:动态申请的内存,不使用了,又没有主动去释放他,就存在内存泄漏
内存泄漏的危害
进程的工作原理:我们启动一个程序,开始我们的任务,然后等任务结束了,我们就停止这个进程。 进程停止后, 该进程就会从进程表中移除。
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源
进程的工作原理:我们启动一个程序,开始我们的任务,然后等任务结束了,我们就停止这个进程。 进程停止后, 该进程就会从进程表中移除。
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程被init接管,子进程退出后init会回收其占用的相关资源
出现内存泄漏的进程正常结束,进程结束时这些内存会还给系统,不会有太大的危害出现内存泄露的进程非正常结束,比如僵尸进程,危害很大,系统会越来越慢,甚至卡死宕机需要长期运行的程序,出现内存泄漏,危害很大,系统会越来越慢,甚至卡死宕机--服务器程序,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死
代码体现
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++
程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak) 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。 系统资源泄漏 指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
如何避免内存泄漏
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状 态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保 证。 2. 采用RAII思想或者智能指针来管理资源。 3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。 4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。 总结一下: 内存泄漏非常常见,解决方案分为两种: 1 、事前预防型。如智能指针等。 2 、事后查错型。如泄漏检测工具。
九、如何一次在堆上申请4G的内存
首先改为64位
int main()
{
void* p = new char[0xfffffffful];
cout << "new:" << p << endl;
cout << sizeof(p) << endl;
return 0;
}
申请成功
首先改为64位
int main()
{
void* p = new char[0xfffffffful];
cout << "new:" << p << endl;
cout << sizeof(p) << endl;
return 0;
}
申请成功
32位下,是不能超过2g的



