目录
operator new 与 operator delete 函数(重点)
定位 new 表达式(placement-new) (了解)
malloc / free 和 new / delete的区别
C/C++内存分布
C语言的回顾
我们都知道,进程地址空间通常分为:
栈、 堆、 数据段(静态区/全局区)、 代码段(常量区)
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int loaclVar = 1;
int num1[10] = { 1,2,3,4 };
char char2[] = "abcd";
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);
}
int main()
{
return 0;
}
以下这些变量在进程地址空间的哪个位置 ?
A. 栈、 B. 堆、 C. 数据段(静态区/全局区)、 D. 代码段(常量区)
1, globalVar:____ 2, staticGlobalVar:____
3, staticVar:____ 4, loaclVar:____
5, num1:____
1~5: C、C、C、A、A
6, char2:____ 7, *char2:____ 8, pChar3:____ 9, *pChar3:____
10,ptr1:____ 11,*ptr1:____
6~11:A、A、A、D、A、B
填空题
1,Sizeof(num1) = __40__;
2, Sizeof(char2) = __5___;
3,sizeof(pChar3) =__4/8__;
4,sizeof(ptr1)=___4/8____;
5,strlen(char2)= ___4____;
6,strlen(pChar3) =___4____;
注意点
1,“abcd”本身是一个字符串常量,它通常存储在静态区(常量区),而不是栈区。
当使用 char char2[] = "abcd"
时,是在栈区为字符数组 char2
分配了足够的空间,并将静态区中字符串常量 “abcd” 的内容复制到栈区的 char2
数组中。
2, char* pChar3 = "abcd";
这种情况下,字符串 "abcd"
本身通常存在于静态区(也称为常量区或只读数据区)。指针 pChar3 是在在栈上分配的,但它所指向的字符串 "abcd"
是在静态区。
char2
数组中的内容可以修改。- 指向字符串常量的
pChar3
所指向的内容不能被修改。
void Test() {
char char2[] = "abcd";
char2[0] = 'x'; // 可以修改
char* pChar3 = "abcd";
// p[0] = 'x'; // 错误,不能修改
// 函数结束时,char2 所占内存自动释放,而 "abcd" 所在的静态区内存不受影响
}
3,在Test作用域中,ptr 和 *ptr, 指针ptr 存放地址,在栈区。 而指针指向的内容 *ptr 是在堆上动态开辟的,存在于堆区
4,在填空题中的 num1不能只看数组中存在多少元素,而是要看数组的大小是多大,该数组可以存放10个整型,所以大小是 4*10 = 40;
5,strlen 和 sizeof 的计算
strlen
函数用于计算字符串的长度,不包括字符串末尾的 '\0' 字符。它通过遍历字符串,直到遇到 '\0' 为止,返回字符的个数。sizeof
是一个操作符,不是函数。对于数组,它返回数组所占的字节数;对于类型,它返回该类型所占的字节数。
全局变量和静态变量的区别 (重点)
链接属性:
- 全局变量具有外部链接属性,可以在其他文件中通过声明后使用(全局变量的在其它文件中都可以使用)。
- 而静态全局变量具有内部链接属性,只能在定义它的文件中使用,其他文件无法访问和使用该静态全局变量(静态全局变量只能在该文件中使用)。
作用域:
- 全局变量的作用域是整个程序,从其定义的位置开始,到程序结束都可见可用。
- 静态变量根据其定义的位置不同,作用域有所不同。静态全局变量的作用域是定义它的文件内;静态局部变量的作用域是定义它的函数内(生命周期是全局的,受到作用域限制)。
存储位置:
- 都存储在静态区
生命周期:
- 全局变量和静态变量的生命周期都是整个程序的运行期间,从程序开始运行时创建,到程序结束时销毁。
数据存储相关示意图
C语言中动态内存管理方式
malloc、calloc、realloc 有什么区别?
void Test()
{
int* p1 = (int*)malloc(sizeof(int));
free(p1);
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 10);
free(p3);
}
malloc
函数:
- 用法:
void *malloc(size_t size)
- 功能:分配指定大小的内存空间,但不会对分配的内存进行初始化,其内容是未定义的。
calloc
函数:
- 用法:
void *calloc(size_t num, size_t size)
- 功能:分配指定数量和大小的内存空间,并将分配的内存初始化为 0。
realloc
函数:
- 用法:
void *realloc(void *ptr, size_t size)
- 功能:调整之前通过
malloc
、calloc
或realloc
分配的内存空间的大小。ptr 是要调整的内存地址, size调整之后新大小,返回值为调整之后的内存起始位置,函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。realloc在调整内存空间的是存在两种情况:原有空间之后有足够大的空间,返回原来的地址,空间不够,会把原空间的数据拷贝到新开的空间,返回新空间的地址。
C++中动态内存管理
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式,通过 new 和 delete 操作符进行动态内存管理。
new 和 delete 操作符内置类型
不能写成这样:int* p4 = new int[10](0); 但是可以这样 int* p5 = new int[10]();
如果需要初始化,使用vector或者遍历数组进行初始化
int main()
{
//C语言 malloc函数
int* p1 = (int*)malloc(sizeof(int));
int* p2 = (int*)malloc(sizeof(int) * 10);
free(p1);
free(p2);
//C++ new操作符
//int* p3 = new int; //申请一个int,没有初始化
int* p3 = new int(10); //申请一个int,4个字节的空间,初始化为10
int* p4 = new int[10]; //申请10个int,40个字节的空间
int* p5 = new int[10](); //这种写法可以将分配的 10 个 int 初始化为 0,括号中不能写其它值
delete p3;
delete[] p4;
delete[] p5;
return 0;
}
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[] 和delete[]
new 和 delete 操作自定义类型
既然以及有了malloc和 free,new和delete的意义何在?
- 1, 对于上面内置类型,它们效果是一样的
- 2, 对于自定义类型,效果就不一样
- malloc 只申请空间,new 申请空间 + 构造函数初始化
- free 只释放空间,delete释放空间 + 析构函数完成清理工作
-
class A { public: A() { _a = 10; cout << "A()" << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { //内置类型,两者没有区别 int* p1 = new int; int* p2 = (int*)malloc(sizeof(int)); //自定义 A* p3 = (A*)malloc(sizeof(A)); //申请空间 A* p4 = new A; //申请空间+构造函数初始化 free(p3); //释放空间 delete p4; //析构函数+释放空间 return 0; }
结论:C++ 中建议使用 new / delete
operator new 与 operator delete 函数(重点)
C++标准库中提供了这两个函数 operator new 与 operator delete
operator new: 该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回,申请空间失败,尝试执行空间不足应对措施,如果对应的措施用户设置了,则继续申请,否则抛异常
operator new
实现类似功能的简化示例代码 (了解)
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) // bad_alloc抛异常
{
//try to allocate size bytes
void* p;
while (p = molloc(size) == 0) // operator new 本质是:malloc申请内存如果申请失败进入循环
{
if (_callnewh(size) == 0)
{
//如果申请内存失败了,这里会抛出bad_alloc类型异常
static const std::bad_alloc nomen;
_RAISE(nomen);
}
}
return (p);
}
new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete全局函数来释放空间
operator new 与 malloc 的区别 (抛异常处理的代码了解)
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
A* p3 = (A*)operator new(sizeof(A));
//operator new 和 malloc 的区别是什么?
// 结论:使用方式都一样,处理错误的方式不一样
//申请空间是有可能失败的,不断申请不断申请/申请的空间很大
size_t size = 2;
void* p4 = malloc(size * 1024 * 1024 * 1024);
cout << p4 << endl; //失败返回 0
//free(p4);
/*void* p5 = operator new(size * 1024 * 1024 * 1024);
cout << p5 << endl;*/ //失败会抛异常(面向对象处理错误的方式)
//如何解决,跳到catch捕获异常的地方
try
{
void* p5 = operator new(size * 1024 * 1024 * 1024);
cout << p5 << endl; //失败会抛异常
//operator delete(p5); 因为是函数
}
catch(exception& e)
{
cout << e.what() << endl;
}
return 0;
}
总结(重点):
- 1,malloc
- operator new ==> malloc + 失败抛异常
- 2,new ==> operator new + 构造函数
- new 比起 malloc 不一样的地方:1,调用构造函数初始化 2,失败抛异常
- 3,这样设计 new 的原因:C++要有一个 new 操作符出来,new有两部分构成:1,开空间 2,是调用构造函数初始化
- 开空间去调谁?自己实现一个malloc吗?如果失败就返回 0,不符合面向对象的特性,所以就有了中间的 operator new
- operator new 是使用 malloc 开空间,失败了以后会抛异常,这样才能达到 new 的特性
- 也可以说operator new 是因为new才产生的,专门为new开空间这个环节服务,失败了就可以抛异常
- 4,delete 比起 free 不一样的地方:delete会调用析构函数清理
- operator delete 和 free没区别,因为释放空间失败直接终止进程,是因为根operator new成对出现而产生的
-
new 和 delete 的实现原理
内置类型
如果申请的是内置类型的空间,new 和 malloc,delete 和 free 基本类似。
不同的地方是:new/delete申请和释放的是单个元素的空间,new[] 和 delere[] 申请和释放的是连续空间,而且new在申请空间失败时会抛异常。
malloc会返回NULL(0)。
自定义类型
new原理
- 1, 调用operator new 函数申请空间
- 2, 在申请空间上执行构造函数,完成对象的构造
delete原理
- 在申请空间上执行析构函数,完成对象中资源的清理工作
- 调用 operator delete 函数释放对象的空间
new T[N]
的原理
- 调用operator new
[]
函数,在operator new[]
中实际调用 operator new 函数完成 N 个对象空间的申请 - 在申请的空间上执行 N 次构造函数
delete[]
清理
- 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
- 调用operator delete
[]
释放空间,实际中operator delete[]
中调用operator delete来释放空间
定位 new 表达式(placement-new) (了解)
定位 new 表达式是在已分配的元素内存空间中调用构造函数初始化一个对象
使用格式:
new(palce_address) type 或者 new(place_address)type(initializer-list)
place_address必须是一个指针,initializer-list是类型的初始化列表
使用场景:
定位 new 表达式在实际中一把是配合内存池使用,因为内存池分配出的内存没有初始化,所以如果自定义类型的对象,需要使用new的定义表达式进行显示调研构造函数进行初始化
//定位 new
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A;
delete p1;
//想模拟new上面的行为
//显示调用了A的构造函数和析构函数
A* p2 = (A*)operator new(sizeof(A));
//对已经存在的一块空间调用构造函数初始化
//比如p2->_a; 有可能是私有的,无法进行初始化,定位new/replacemenet new
new(p2)A(10);// new(空间的指针)类型(参数) //两个步骤合起来就是new的行为
//模拟delete 析构函数可以显示调用,因为是公有的
p2->~A();
operator delete(p2);
return 0;
}
其实这些操作就是new的操作,只不过operator new 没有调用构造函数和析构函数,所以只能使用定位 new 表达式完成。
常见面试题
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 在释放空间前会调用析构函数完成空间中资源的清理
综上所述:
malloc 和 new
1、使用效果
new 会调用构造函数,失败抛异常,malloc失败会返回 0.
2、概念性质
malloc 是一个函数,new 是一个操作符
3、使用方法
malloc用法: 参数传字节数,返回值是 void*,new后面跟申请对象的类型,返回值是类型的指针。
内存泄漏
什么是内存泄漏? 内存泄漏的危害
什么是内存泄漏: 内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
普通的程序是没有内存泄漏的危害的,进程结束,系统就正常回收了内存。
哪些程序不释放危害就大???长期运行的程序(游戏服务),或者设备的内存本身很小,也有危害。
void MemoryLeaks()
{
//1,内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2,异常安全问题
int* p3 = new int[10];
Fun(); //这里Func函数抛异常导致 delete[] p3未执行, p3没被释放
delete[] p3;
}
内存泄漏的分类(了解)
- 堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc/calloc/realloc/new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
- 系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳。
如何检测内存泄漏(了解)
1,在 Linux 下,以下几种工具可以用于检查内存泄漏:
Valgrind
:这是一个功能强大的工具集,其中的Memcheck
工具可以检测内存访问错误和内存泄漏。mtrace
:这是一个 glibc 提供的工具,可以跟踪malloc
和free
调用,帮助检测内存泄漏。GDB
:通过在程序中设置断点和检查内存状态来辅助查找内存泄漏。
2,在windows下使用第三方工具: VLD工具
如何避免内存泄漏
工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证
2.采用RAII思想或者智能指针来管理资源。
3.有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4.出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。
2、事后查错型,如泄漏检测工具。
如何一次在堆上申请4G的内存
这是在32位机器下申请空间失败
如何做到在堆上申请 4G内存呢?
将程序迁移到 64 位的系统和编译器上
原因如下:
在 32 位系统中,可用的地址空间通常被限制在 4GB 左右。这 4GB 的空间还要被操作系统内核、其他系统资源以及正在运行的其他进程所共享,所以单个进程实际上可用的连续内存空间往往远小于 4GB ,通常在 2GB 或更少,具体取决于操作系统的设置。
而 64 位系统的地址空间大幅扩展,可以达到数十亿 GB 甚至更多。这使得在 64 位系统中,为单个进程分配数 GB 甚至更大的连续内存区域成为可能。