1. 前言
C++的内存模型很重要!C++的内存模型很重要!C++的内存模型很重要!
重要的事情要说三遍。
作为一名C++程序员,如果不知道内存分布的话很尴尬的!(好吧,我就是那个不知道的人。最近面试的时候基本都有一道内存分布的题。从常量,变量,类,类的虚函数在内存中的存储,堆栈的使用等,问题变化万千!T_T)
2. 内存分区
C++的内存可以分成5个区域:
(1) 堆(heap)
堆,编译器不用去管,可以使用new和malloc来申请内存,但必须在不使用的时候使用delete和free释放掉。(网上有人说malloc放在自由存储区中,我这里做个测试,测试代码如下)
int* d = new int(5);
int* e = (int*)malloc(sizeof(int));
printf("The location of new point: %p\n",d);
printf("The location of malloc point: %p\n",e);
在ubuntu系统下的结果如下:
The location of new point: 0x199dc20
The location of malloc point: 0x199dc40
注意一下,使用new和malloc出来的内存分布是在同一个区里面的,也就没有什么自由存储区和堆了,我这里就简称堆吧。
(2) 栈(stack)
栈中存放着局部变量,函数参数,局部常量。
有人可能会很好奇,什么,局部常量不是在常量区吗?我这里也做一下测试:
int f()
{
const int b = 2;
printf("the location of the const variable in function: %p\n",&b);
return 1;
}
int main()
{
int a = 2;
f();
printf("the location of the stack: %p\n",&a);
return 0;
}
(英语有点差,不要在意)运行的结果如下:
the location of the const variable in function: 0x7fffd82f1bd4
the location of the stack: 0x7fffd82f1bf4
看到没有,局部常量也在栈中,而不是在常量存储区中。
(3) 静态存储区
主要存储全局静态变量,局部静态变量,全局变量,以及虚函数表,没错,你没听错,就是虚函数表。
下面我继续测试一下:
class A
{
public:
int a;
virtual void f()
{
cout << "1" << endl;
}
void location()
{
cout << "the location of the virtual function: ";
cout << (int*)*(int*)this+0 << endl;
}
};
int f()
{
static int b = 2;
printf("the location of the static variable in function: %p\n",&b);
return 1;
}
static int b = 2;
int main()
{
A a = A();
printf("the location of the static area: %p\n",&b);
f();
a.location();
return 0;
}
可能有人不知道(int *)*(int *) this + 0,好吧,其实我也不是那么理解。网上说他的意思就是取这个指针的首地址,也就是虚函数表的首地址。先这么测试,之后再说一下反汇编测试。运行的结果如下:
the location of the static area: 0x602068
the location of the static variable in function: 0x60206c
the location of the virtual function: 0x601de8
看到没有,全局静态,局部静态,虚函数都在静态存储区中,如果你还是觉得很好奇,为什么那个指针就可以指向虚函数,那我们可以通过linux的反汇编来进行查找,在命令行运行如下命令:
g++ -g main.cpp -o test
objdump -S test
就可以看到反汇编出来的代码,有点多,我就不全部放出来了,就说虚函数的地址就好了。
首先是找到类,在类里面一开始有一些初始化操作,里面就有虚函表的地址,像上面,601e08就是虚函数表的首地址。
现在是不是对虚函数表的位置有点了解啦!其实你只要这么想,一份类里面只有唯一的虚函数表,那么这东西是不是很像静态成员,一份类里面只有一份,不同对象共用一份。所以这么想的话,也就很清楚的知道虚函数在静态存储区中了。
还有一类比较特殊,也是放在静态存储区中,就是全局常量指针,又好像不能这么说,举个例子吧:
const char* p = "1234";
const char ch[] = "1234";
猜猜看,指针p和ch的首地址放在哪里?可能你会回答常量区啊,对不起,指针p是放在静态存储区的,它被看成是全局的变量,这个道理就很类似下面这个代码:
int *p = new int(5);
指针p如果在函数中,那么指针p是放在栈中,而不是堆中,指针指向的地址才是在堆中。
好,回到上面那个例子,运行测试以下可以得到它们的地址分别为:
所以说静态存储区很重要,基本上你写的代码离不开静态存储区。除非你不声明全局变量,也不声明静态变量,也不用虚函数,也不用全局指针。
(4) 常量存储区
有人可能会觉得这个区有什么用,如果不写一些常量的话?那你就大错特错了。这个区主要保存什么东西呢?全局常量,函数指针,常量数组,对函数指针,你可能有很好奇,哈,你说的什么鬼,为什么我从来都没听说过,函数还需要存储。函数就是需要存起来,函数也有函数指针,函数指针实际上就是常量指针。
依然来举几个例子:
const int a = 1;
void f()
{
cout << 1 << endl;
}
int main()
{
cout << "The location of the const Variable: " << &a << endl;
cout << "The location of the function f: ";
printf("%p\n",f);
return 0;
}
代码也很简单,就是输出函数f的地址,结果如下:
很清楚的就可以知道,函数指针位于常量存储区中,这表明函数本身就是常量指针。这也是,如果不是敞亮的话,那岂不是可以随随便便就把这个函数指向下一个函数,那是绝对不可以的,所以函数指针设置为常量才是最合理的。
(5) 代码区
就是存放代码的地方,这个就不多说,实际上就是一个只读的区域,和常量区一样,只是这个区域的指针可以执行而已。
3. 各分区的作用
(1) 堆
这个的作用就是你new,malloc出来的东西都放在这里,有什么用呢?很简单,栈一般比较小,栈不仅要放函数递归调用的参数,还需要放一些函数内部的参数,本身的开销就比较大,如果不适用堆的话,很容易就会爆栈。而堆的内存一般比较大,当然,像那种一次性申请5,6G内存的还是省省吧。此外,在这个区中申请的内存一定要记得释放,一定要,不然会内存泄露。
(2) 栈
这个区域是有编译器自动申请和释放的,它就像数据结构的栈,指针一直往往上移动,退出函数的时候就会往回退。存放函数的参数,函数内部变量,函数一些寄存器的信息,函数返回的地址等信息。
(3) 静态存储区
这个上面已经讲的很仔细了,主要用于存放虚函数,局部/全局静态变量,全局常量指针等。
(4) 常量存储区
这个区域主要存放着函数的指针,全局常量等信息
(5) 代码区
放代码的,额。
4. 总结
讲到这里差不多,以后有需要在进行补充。就写一句比较土的总结:
没堆,申明毛变量,没栈,调用毛函数,没静态存储区,写个毛运行多态,没常量存储区,写个毛函数,没代码区,写个毛代码。
有点土,不过希望能给你们一些好的收获。
参考链接:
https://www.cnblogs.com/Stultz-Lee/p/6751522.html