内存布局------c++程序设计基础、编程抽象与算法策略
图中给出了在一个典型c++程序中如何组织内存的框架。程序中的指令(在底层都是按位存储的)、全局变量、静态对象和只读常量往往被存储在静态去(static area)(第二个图中的数据段、代码段。值得注意的一点是:代码段中存储的是可执行的代码和只读常量,很多人看到代码段就认为里面只有代码,数据段里面才是存储数据的,其实不是这样的。),该区域位于地址编址号较小的接近机器地址空间的开始处。该区域所分配的内存量在程序运行期间不会发生改变。关于全局对象,是在main()函数执行前就分配好了的。其实,在main()函数中的显示代码执行之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局对象的的构造及初始化工作。而在main()函数结束之前,会调用由编译器生成的exit函数,来释放所有的全局对象。比如下面的代码:
int main() { … …// 显式代码 }
实际上,被转化成这样:
int main() { _main(); //隐式代码,由编译器产生,用以构造所有全局对象 … … // 显式代码 … … exit() ; // 隐式代码,由编译器产生,用以释放所有全局对象 }
刚才讲的是静态存储区中的全局对象,那么,局部静态对象了?局部静态对象通常也是在函数中定义的,就像栈对象一样,只不过,其前面多了个static关键字。局部静态对象的生命期是从其所在函数第一次被调用,更确切地说,是当第一次执行到该静态对象的声明代码时,产生该静态局部对象,直到整个程序结束时,才销毁该对象。
还有一种静态对象,那就是它作为class的静态成员。考虑这种情况时,就牵涉了一些较复杂的问题。
第一个问题是class的静态成员对象的生命期,class的静态成员对象在初始化静态成员对象就产生了,直到整个程序结束时,才销毁该对象。
第二个问题是,当出现下列情况时:
#include<iostream> using namespace std; class a {public:static int* g; }; int* a::g = new int; class b : public a{}; class c : public b{}; int main() { cout << a::g << endl; cout << b::g << endl; cout << c::g << endl; }
输出结果
00E89F10 00E89F10 00E89F10
继承类和基类共用一个静态成员变量。
内存中的最高地址区表示栈区(stack area)。当你的程序每调用一个函数或者方法,计算机(编译器自动管理)就会在这个内存区创建一个新的栈帧。当函数返回时,所创建的栈帧会被撤销,以为后续的函数调用所需的栈帧释放内存。通常栈空间容量比较小,一般是1MB~2MB,可设置大小。栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。栈内存的分配是连续的,类似于平时我们所说的栈,如果还不清楚,那么就把它想成数组,它的内存分配是连续分配的,即,所分配的内存是在一块连续的内存区域内.当我们声明变量时,那么编译器会自动接着当前栈区的结尾来分配内存.栈向下,向低地址方向增长。对于栈,它是一个先进后出的队列,进出一一对应,不会产生碎片。只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
处于栈区和静态区之间的内存区域被称为堆区(heap area)。(程序员手工管理)该区域会在程序运行时请求更多内存的时候发挥作用。唯一的方法是用new(malloc)。相比于栈空间,堆的容量要大得多。一般来讲在32位系统下,堆内存可以达到4G的空间。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块 内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的 大小,系统会自动的将多余的那部分重新放入空闲链表中。频繁的new/delete会造成大量碎片,使程序效率降低。堆向上,向高地址方向增长。用malloc 或new 申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL 的内存。
内联函数的临时变量存在main的栈上,相当于main的临时变量。
这是一个前辈写的,非常详细 //main.cpp int a = 0; 全局初始化区 char *p1; 全局未初始化区 main() { int b; 栈 char s[] = “abc”; 栈 char *p2; 栈 char *p3 = “123456”; 123456\0在常量区,p3在栈上。 static int c =0; 全局(静态)初始化区 p1 = (char *)malloc(10); p2 = (char *)malloc(20); 分配 得来得10和20字节的区域就在堆区。 strcpy(p1, “123456”); 123456\0放在常量区,编译器可能会将它与p3所指向的”123456”优化成一个地方。 }
c++程序设计基础、编程抽象与算法策略
另有说法是5个区
作者:Acjx
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.在网上查资料看到:两种说法,一种是分为 栈,堆,自由存储区,全局,常量;第二,堆,栈,全局,常量,程序代码区,到底哪一个是对的呢?我个人一直是理解堆和自由存储区是一种东西,请解答一下疑惑
2,另外一个问题:类的成员函数,存放在什么地方呢?
1 .rodata段:存放只读数据,比如printf语句中的格式字符串和开关语句的跳转表。也就是你所说的常量区。
例如,全局作用域中的 const int ival = 10,ival存放在.rodata段
再如,函数局部作用域中的printf("Hello world %d\n", c);语句中的格式字符串"Hello world %d\n",也存放在.rodata段
2 .text段:存放已编译程序的机器代码。
注意:程序加载运行时,.rodata段和.text段通常合并到一个Segment(Text Segment)中,操作系统将这个Segment的页面只读保护起来,防止意外的改写。
3 .data段:存放已初始化的全局变量。而局部变量在运行时保存在栈中,既不出现在.data段,也不出现在.bss段中。就是你所说的全局区。例如:全局作用域中的int ival = 10,static int a = 30,以及局部作用域中的static int b = 30,这3个变量均存放在.data段中。注意,局部作用域中的static变量的生命周期和其余栈变量的生命周期是不同的。
4 .bss段:存放未初始化的全局变量。在目标文件中这个段不占据实际的空间,它仅仅是一个占位符,在加载时这个段用0填充。目标文件区分初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。全局变量如果不初始化则初值为0,同理可以推断,static变量(不管是函数里的还是函数外的)如果不初始化则初值也是0,也分配在.bss段。
例如,全局作用域中的int ival; ival显然存放在.bss段
注意:.data和.bss在加载时合并到一个Segment(Data Segment)中,这个Segment是可读可写的。
5. 栈:函数的参数和局部变量是分配在栈上(但不包括static声明的变量)。在函数被调用时,栈用来传递参数和返回值。由于栈的后进先出特点,所以栈特别方便用来保存/恢复调用现场。
6. 堆:用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc/free等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张)/释放的内存从堆中被剔除(堆被缩减)
现在回答你第二个问题,类的成员函数存放在代码段。
在C++中,如果类中有虚函数,那么它就会有一个虚函数表的指针__vfptr,在类对象最开始的内存数据中。之后是类中的成员变量的内存数据。注意要考虑对齐。