1.C/C++的内存分布

【说明】
1.栈:主要用于存储非静态的局部变量/函数参数/返回值等,栈是向下生长的。
2.堆:堆主要是程序运行时的动态内存分配,堆是向上生长的,堆是向上生长的,但是地址也不一定越来越大,因为如果前面的内存被释放了,就可能被拿来继续使用,这样的话就会变小
3.数据段:全局变量或者静态数据
4.代码段:可执行的代码/只读常量

例题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int globalvar = 1;
static int Staticglobalvar = 1;
int main()
{
static int staticvar = 1;
int loacalvar = 1;
int mun1[10] = { 1,2,3,4 };
char ch1[] = "rxj is so happy";

const int x = 10;//const只是修饰了x的内容,而不是说x就存放在常量区了

const char* ch= "rxj is so happy";//一个字符串会返回一个这串字符的首地址
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4,sizeof(int));//第一个参数是元素的个数,第二个参数是元素的大小
int* ptr3 = (int*)realloc(ptr1,sizeof(int) * 4);
return 0;
}

1.选择  a栈,b堆,c代码段(常量区),d数据段(静态区)

   globalvar存放在:——(数据段)       Staticglobalvar存放在:——(数据段)

   staticvar存放在:——(数据段)         localvar存放在:——(栈)

   num1存放在:——(栈)

   ch1存放在:——(栈)                         *ch1存放在:——(栈)

   ch存放在:——(栈,那个const修饰的是他指的内容)      *ch存放在:——(代码段)

   ptr1存放在:——(栈)                         *ptr1存放在:——(堆)

   x存放在:——(栈)

补充:const修饰的是变量里面的内容,和变量存放在那里没有关系


2.C语言中动态内存管理方式malloc,realloc,calloc以及free

1
2
3
4
5
6
7
8
9
10
int main()
{
int* gt1 = (int*)malloc(sizeof(int) * 4);//malloc开辟的空间不会初始化,而且参数只有一个
int* gt2 = (int*)calloc(4, sizeof(int));//calloc有两个参数,第一个是类型的个数,第二个是类型,而且calloc开辟的空间会初始化为0
int* gt3 = (int*)realloc(gt1, sizeof(int) * 8);//第一个参数是原空间的首地址的位置,第二个参数是修改以后的空间的大小
//free(gt1);注意:1.异地扩容:gt1的空间realloc会自动释放了 2.原地扩容就没有释放的必要
free(gt3);//
free(gt2);//
return 0;
}

面试题:

1.realloc、malloc、calloc之间的区别?

malloc和calloc类似,主要是参数和初始化的区别,malloc有一个参数,并且不会初始化。calloc有两个参数,并且会把空间初始化为0。realloc会扩容,如果空间不够,会开辟新空间,并且把旧空间销毁了.

2.malloc的实现原理?

留一个疑问


3.C++内存管理方式

c++有自己的内存管理方式,那就是两个关键字new和delete,但是不要小看这两个关键字,因为他们可以进行一些malloc不能进行的东西,比如说1.对开辟的空间进行初始化  2.返回值不需要进行强制类型转换   3.自定义类型可以自动调用构造函数,也就是说可以将类里面的成员也初始化了

3.1new/delete操作内置类型

1
2
3
4
5
6
7
8
9
int main()
{
int* p1 = new int;
int* p2 = new int(3);
int* p3 = new int[4] {1, 2, 3, 4};
delete p1;
delete p2;
delete[] p3;
}

总结一下:关键字new+类型+个数(一个的话就不用写)+初始化的值(不初始化的话也不需要写)。

补充:调试的时候如何通过数组名访问数组里面的数值呢?数组名+,+数字

3.2new和delete操作自定义类型

对于自定义类型,new开辟空间以后,还会自动调用构造函数,delete会调用析构函数!!!(这个才是new和delete最大的区别所在)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class A
{
public:
A()
:_a(5)
,_b(4)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
int _b;

};
int main()
{
A* a1 = new A;
delete a1;
return 0;
}

 

new,delete,malloc,free除了在调用构造函数和析构函数有区别外,还有一个区别就是malloc在申请失败时返回的是空,而new则是报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main()
{
for (int i = 0;i < 100000;i++)
{
int* p = (int*)malloc(sizeof(int) * 1024 * 1024*100);
if (p)
{
cout << i <<"-" << p << endl;
}
else
{
break;
}
}
return 0;
}

malloc不会直接报错,然而new如果申请失败会抛出异常,需要用try和catch捕捉。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
try
{
for (int i = 0;i < 100000;i++)
{
int* p = new int[1024 * 1024 * 100];
if (p)
{
cout << i << "-" << p << endl;
}
else
{
break;
}
}
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}

有一个小技巧,我们如何调试到那个81次的错误呢?我们可以使用打断点,手动写一个if条件语句,写一个赋值语句来当做断点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define _DEBUG_RXJ
int main()
{
try
{
for (int i = 0;i < 100000;i++)
{
#ifdef _DEBUG_RXJ
if (i == 81)
{
int x = 10;
}
#endif
int* p = new int[1024 * 1024 * 100];
if (p)
{
cout << i << "-" << p << endl;
}
else
{
break;
}
}
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}

 虽然我们可以打断点了,但是那个代码非常的冗余,难看,但是我们又很需要它,我们该怎么办呢?其实可以用#ifdef _DEBUG_RXJ 和#endif包裹住不需要的代码,这样的话在编译过程中,这段代码就不会被编译,只有在使用了define _DEBUG_RXJ的时候,这段代码才会被放出来。

注意:new由两部分工作构成,第一部分是开辟一个类的空间并且把地址返回给指针,第二部分是调用构造函数,如果成员变量里面需要开空间的话,构造函数就会给成员开空间,所以一共进行了两步,开辟了两个空间。delete会先调用析构函数把成员变量开辟的空间销毁,然后再销毁开辟的类的大小的空间。


4.operator new和operator delete函数

operator new是对malloc的封装,它与malloc的不同就是他在失败的时候会抛异常,new则又是operator new的封装,new在开辟自定义类型空间时会调用构造函数

operator delete是对free的封装。delete与operator delete不同的是,delete会先调用析构函数,先释放给成员变量开辟的空间,然后再释放new开辟的空间。

所以new先开空间,构造函数再开辟自己的空间。delete先调用析构函数释放成员的空间,然后再销毁new本身开辟的空间

提问:new和free可以混合使用吗?在你掌握原理以后其实可以的(比如说内置类型),但是最好不要乱搞。

补充一点:

A* a = new A [10];

那么编译器一共会开辟过少个字节呢?其实是4+10*sizeof(A);

为什么呢?

其实他还开批了一个空间去存放int类型的10,这一步是为了在调用delete[]时知道开辟了多少字节。

总结一下:
new对应的就是delete,因为此时delete只调用一次析构函数

new X[]对应的是delete[],因为new在开辟空间的时候会多开辟一个整型用来存放开辟的类型的个数,delete[]就知道要往前4个字节去寻找要调用几次析构函数。但是如果你没有生成析构函数,那么new就不会多开辟空间去告诉delete调用几次析构函数。所以尽可能去匹配使用

 


5.定位new的表达式

定位new的作用:显示的调用构造函数(首先我们要知道构造函数是不能显示调用的)。

使用格式:new(指针)+类型

定位new一般配合着内存池使用。如果是自定义类i小姑娘的对象,需要使用new的定义表达式进行初始化


6.malloc、new、free、delete的区别

一、用法方面

1.malloc和free是函数,new和delete是操作符

2.malloc需要自己计算空间的大小。new只需要加上类型,如果要开辟多个对象,用[]加上数字

3.malloc申请的空间不能初始化,而new开辟的空间可以初始化

4.malloc的返回值是void*,而new的返回值就是他本身后面跟的类型的指针

5.malloc在开辟失败的时候会返回nullptr,而new则是抛异常

二、功能方面

1.申请自定义类型的对象时,malloc和free只会开辟空间,不会调用构造函数和析构函数,而new和delete会自动调用构造函数和析构函数。