1 栈(stack)
通常是用于那些在编译期间就能确定存储大小的变量的存储区,用于在函数作用域内创建,在离开作用域后自动销毁的变量的存储区。通常是局部变量,函数参数等的存储区。他的存储空间是连续的,两个紧密挨着定义的局部变量,他们的存储空间也是紧挨着的。栈的大小是有限的,通常Visual C++编译器的默认栈的大小为1MB,所以不要定义int a[1000000]这样的超大数组。主要用来存放局部变量, 传递参数, 存放函数的返回地址。.esp 始终指向栈顶, 栈中的数据越多, esp的值越小。
2 堆(heap)
通常是用于那些在编译期间不能确定存储大小的变量的存储区,它的存储空间是不连续的,一般由malloc(或new)函数来分配内存块,并且需要用free(delete)函数释放内存。如果程序员没有释放掉,那么就会出现常说的内存泄漏问题。需要注意的是,两个紧挨着定义的指针变量,所指向的malloc出来的两块内存并不一定的是紧挨着的,所以会产生内存碎片。另外需要注意的一点是,堆的大小几乎不受限制,理论上每个程序最大可达4GB。
3 数据区(.data和.bss)
包括初始化数据区(.data)、未初始化数据区(.bss)两种,和“栈”一样,通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。未初始化数据区(BSS):用于存放程序的静态变量,这部分内存都是被初始化为零的;而初始化数据区用于存放可执行文件里的初始化数据。这两个区统称为数据区。初始化数据区中包含一个常量存储区(.rodata)保存字符串。
4 代码区(text)
是个只读区,存放了程序的代码。任何尝试对该区的写操作会导致段违法出错。代码区是被多个运行该可执行文件的进程所共享的。
5 总结
根据上面的内容,分别将栈和堆、全局/静态存储区和常量存储区进行对比,结果如下。
表1 栈和堆的对比
栈 | 堆 | |
存储内容 | 局部变量 | 变量 |
作用域 | 函数作用域、语句块作用域 | 函数作用域、语句块作用域 |
编译期间大小是否确定 | 是 | 否 |
大小 | 1MB | 4GB |
内存分配方式 | 地址由高向低减少 | 地址由低向高增加 |
内容是否可以修改 | 是 | 是 |
| 全局/静态存储区 | 常量存储区 |
存储内容 | 全局变量、静态变量 | 常量 |
编译期间大小是否确定 | 是 | 是 |
内容是否可以修改 | 是 | 否 |
注意:
1) 堆向高内存地址生长;
2) 栈向低内存地址生长;
3) 堆和栈相向而生,堆和栈之间有个临界点,称为stkbrk。
1、一条进程在内存中的映射
假设现在有一个程序,它的函数调用顺序如下:
main(...) ->; func_1(...) ->; func_2(...) ->; func_3(...),即:主函数main调用函数func_1; 函数func_1调用函数func_2; 函数func_2调用函数func_3。
当一个程序被操作系统调入内存运行, 其对应的进程在内存中的映射如下图所示:
注意:
l 随着函数调用层数的增加,函数栈帧是一块块地向内存低地址方向延伸的;
l 随着进程中函数调用层数的减少(即各函数调用的返回),栈帧会一块块地被遗弃而向内存的高址方向回缩;
l 各函数的栈帧大小随着函数的性质的不同而不等, 由函数的局部变量的数目决定。
l 进程对内存的动态申请是发生在Heap(堆)里的。随着系统动态分配给进程的内存数量的增加,Heap(堆)有可能向高址或低址延伸, 这依赖于不同CPU的实现,但一般来说是向内存的高地址方向增长的。
l 函数的栈帧:包含了函数的参数(至于被调用函数的参数是放在调用函数的栈帧还是被调用函数栈帧, 则依赖于不同系统的实现)。函数的栈帧中的局部变量以及恢复该函数的主调函数的栈帧(即前一个栈帧)所需要的数据, 包含了主调函数的下一条执行指令的地址。
2、 函数的栈帧
函数调用时所建立的栈帧包含下面的信息:
1) 函数的返回地址。返回地址是存放在主调函数的栈帧还是被调用函数的栈帧里,取决于不同系统的实现;
2) 主调函数的栈帧信息, 即栈顶和栈底;
3) 为函数的局部变量分配的栈空间;
4) 为被调用函数的参数分配的空间取决于不同系统的实现。
注意:
l BSS区(未初始化数据段):并不给该段的数据分配空间,仅仅是记录了数据所需空间的大小。
BSS段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。当这个内存区进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。所以说BSS不占据程序空间的意思是在编译程序时此段并不存在,在运行时动态创建一个空间,并由系统维护。
l DATA(初始化的数据段):为数据分配空间,数据保存在目标文件中。
不同类型的变量在内存中的位置:
1,局部变量、函数参数存放在栈上。静态局部变量,并不是在调用函数时分配,在函数返回时释放,而是像全局变量一样静态分配在.data数据段,但它的作用域只在函数中起作用。
2,堆,给动态分配内存使用。
3,全局变量、静态变量位于.data数据段;未初始化变量则位于.bss未初始化数据段。
4,const修饰的全局变量在.rodata只读数据段(const变量在定义时必须初始化,如果未初始化将被设为0或空),只读数据段在和.text同一个segment
5,代码段即存储程序文本。指令指针中的指令就从这里取得。这个段一般是可以被共享的:你可以使用两个编辑器(vi?)来编辑文本,则它们共享一个代码段。
全局变量初始化和未初始化的区别
我们都知道未初始化的静态变量或全局变量将被初始化为空串或0;未初始化的局部变量的值不确定。
全局变量不初始化,则默认为0,存放在.bss数据段中。
编译器在编译的时候针对这两种情况会产生两种符号放在目标文件的符号表中,对于初始化的,叫强符号,未初始化的,叫弱符号。
连接器在连接目标文件的时候,如果遇到两个重名符号,会有以下处理规则:
1、如果有多个重名的强符号,则报错。
2、如果有一个强符号,多个弱符号,则以强符号为准。
3、如果没有强符号,但有多个重名的弱符号,则任选一个弱符号。
基于以上规则,使用以下程序测试
#include <stdio.h>
int global_int;
void setInt();
int main()
{
printf(“未初始化全局变量: %d\n”, global_int);
setInt();
printf(“改变全局变量: %d\n”, global_int);
}
int global_int;
void setInt()
{
global_int = 2;
}
以上程序正常运行。如果将第2行和第12行的global_int任意一个初始化,程序也正常运行。但将第2行和第12行的gloal_int都初始化,将会出现:error: redefinition of ‘global_int’;
所以我们尽量初始化全局变量。
除了连接有区别外,它们存储的位置也不一样:初始化的全局变量被保存在data数据段中;而未初始化的全局变量被保存在.bss数据段中。
class K
{
public:
K(){k = 12;}
~K(){}
int k;
};
//类的使用//... K kTemp;
printf("%d--%d\n",&kTemp,&kTemp.k);
printf("%d--%d\n",sizeof(K),sizeof(kTemp.k));
int *i = (int*)(&kTemp);
int w = *i;
printf("%d\n",w);
1310588--1310588
4--4
12
class A
{};
class A
{
public:
int i;
}
class A
{
public:
int i;
int l;
}
class A
{
public:
int i;
int l;
int add(int x,int y){return (x+y);}
};
class A
{
public:
int i;
int l;
static int s;
virtual void Say(){};
int add(int x,int y){return (x+y)};
};
int *p = (int *)a;
p++;
*p = 1;//把i的值赋为1.