本文主要介绍全局变量、局部变量、通过malloc开创的动态内存以及函数传参的参数变量等在C语言中是如何存储的,并介绍几个有趣的小例子用以加深理解。
今天讨论的主要重点在于栈,所以对其他部分略简单。每个程序的内存分布只是一个逻辑上的,还要通过一层映射到真正的物理内存上,所以不需要在意不同程序看似使用了同一地址的内存,这里属于操作系统的任务,在此不再较多阐述。
栈,存放C语言程序中的局部非静态变量和函数参数变量的位置。
堆,存放通过malloc开创的内存。
数据段,存放被显式初始化的全局或静态变量。
bss,未被显式初始化的全局或静态变量,从而被默认初始化为0。
代码段,存放代码本身,一般为只读。
在实验测试之前,首先了解什么是小端系统、什么是大端系统。小端系统即数据的低位放在低地址处。例如int i=0x33445566;那么0x66应当在低地址(内存以字节为最小存储单元)。
可以运行一个小程序来简单判断系统模式。
void f()
{
int i=0x33445566;
char *c=(char *)&i;
if (*c==0x33)
printf("big-ending\n");
else printf("small-ending\n");
}
我的系统是小端系统,以下举例都以小端系统,以GCC运行为例。
对于有初始化的全、静变量和被默认初始化的全、静变量储存位置是不一样的。(没有被显式初始化的值会被默认初始化为0)
被默认初始化的全、静变量存储在bss。
有初始化的全、静变量存储在数据段。
可以观察下面例子:
#include <stdio.h>
#include <stdlib.h>
int i,j=3,k;
void f()
{
static int i=3;
static int j,k,ii=4;
printf("%p %p %p %p\n",&i,&j,&k,&ii);
}
int main()
{
printf("%p %p %p\n",&i,&j,&k);
f();
return 0;
}
运行结果:
00405058 00402000 0040505C
00402004 00405008 0040500C 00402008
可以观察到,全局变量中被默认初始化的变量i,k在一块,而j在另一片区域。
静态变量,i和ii与全局变量j在一片区域,静态变量j,k和全局变量i,k在一片区域。
我只能粗略的说,默认初始化的全、静变量在一块区域,有初始化的全、静变量在另一块区域。但不一定会满足由定义顺序由低到高存储这一点。猜测储存的具体位置可能与变量名有关。
例如:
#include <stdio.h>
#include <stdlib.h>
int zz,i=3,kk;
int main()
{
printf("%p %p %p\n",&zz,&i,&kk);
return 0;
}
运行结果:
00405054 00402000 00405050
可以看到,kk的地址比zz小。而上一个例子中,后定义的k的地址是大于先定义的i的。
但是,无论如何,数组变量内部一定满足地址由低到高。例如:
short a[100];
a[99]地址=a[98]地址+1=……=a[1]地址+98=a[0]地址+99
注:c语言中地址增加n,实际上地址增加了sizeof(类别)*n。即假定a[0]地址为0x00000000,那么*(a+5),其中a+5等于0x00000000+sizeof(short)*5=0x0000000a。
全局变量和静态变量的具体存储规则,如果有不一样的见解或是更为详细的介绍,请留言。
在函数中的定义的普通变量的存储方式就简单的多(非static变量)。无论是否初始化,由定义顺序依次入栈。由于是入栈,先定义的变量在内存的高地址,后定义的变量在内存低地址处。函数中定义变量,没有被显式初始化的话,则将是一个未知值(该内存单元原来的值)。
注:大小端系统是指具体一个数值内部的存储地址是低位在低地址还是高位在低地址。和存放在堆、栈、数据段等没有一丝关系。
例如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int zz,i=3,kk;
printf("%p %p %p\n",&zz,&i,&kk);
return 0;
}
运行结果:
0028FF1C 0028FF18 0028FF14
可以看到,地址由定义顺序依次入栈。
基于此,可以写出一些有趣的例子。
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
int a[10];
for (i=0;i<=10;++i)
a[i]=1;
return 0;
}
以上这个程序,一看就知道数组访问过界。仔细分析会发现,a[10]的内存地址就是i的地址。所以,循环中a[10]=1,将改变i的值。最后,结果就是这是个死循环程序23333。
对于函数参数中的变量,是从右往左依次入栈,接着入栈一些调用函数的需要保存的寄存器的值,然后是函数内部定义的变量由定义顺序依次入栈。
例:
#include <stdio.h>
#include <stdlib.h>
void f(int x,int zz,int y)
{
int k,i,j;
printf("%p %p %p\n%p %p %p\n",&y,&zz,&x,&k,&i,&j);
}
int main()
{
f(3,4,5);
return 0;
}
运行结果:
0028FF18 0028FF14 0028FF10
0028FEFC 0028FEF8 0028FEF4
可以看到,y,zz,x地址值由高到低(每次减少sizeof(int))。
并且,k,i,j地址值也是由高到低(每次减少sizeof(int))。
正好就是入栈的顺序。
但是在传入参数中入栈的最后一个变量x和函数中定义的第一个变量k,可以发现地址减少量不止一个sizeof(int),那是因为其中还需要保存PC等寄存器的值。具体保存了些什么,有兴趣的人可以看下汇编代码。需要知道的是,&k+1即第一个被定义的变量之前入栈的是PC寄存器的值。也就是说,函数内部定义变量之前,最后一个入栈的是PC寄存器的值。
所以可以通过这个特性,写出一个无限被调用的函数。
#include <stdio.h>
#include <stdlib.h>
void f(int x,int zz,int y)
{
int i,j;
for (j=1;j<=4;++j)
if (j==1)
*(&i+1)-=4;
}
int main()
{
f(3,4,5);
return 0;
}
函数中最好不要直接写成*(&i+1)-=4,不然太简单,运行时会被检测出来然后被中止程序。这个程序会无限运行下去的主要原理在于,调用函数f(3,4,5)的时候入栈了下一条指令PC的值。然后被我通过*(&i+1)-=4给修改了,函数结束出栈时还原PC的值。此时PC被减了4,又是调用函数这条指令。所以,将不断调用函数。(PC寄存器的意义请百度)
#include <stdio.h>
#include <stdlib.h>
void g(int x)
{
int a[4];
int i;
for (i=0;i<=4;++i)
a[i]-=4;
}
int main()
{
g(3);
return 0;
}
以上C代码也是同样原因,无限调用函数。
通过malloc开创的变量存储在堆中,其实在实现的时候,会分配一块内存来维护开创的大小。即malloc(sizeof(int)*4),肯定会分配sizeof(int)*4的大小的内存。还会分配一块4字节(不一定)大小的内存来储存sizeof(int)*4即16这个数值变量。每次realloc的时候会更新这个数值变量,这样在free的时候,才能准确知道需要释放多少大小的内存空间。至于这个数值变量保存在哪?有得说是开创的内存地址的前4个字节,可能在实现上会是在前好几个字节。不过我测试中,没有发现存储该变量的位置,可能是具体实现机制发生了一些变化。
原文:http://www.cnblogs.com/zebfff/p/5110154.html