我们可以简单的把一个编译好程序认为是由代码区和数据区组成,这是因为数据区又可以分区已初始化数据区,未初始化数据区,虽然这样叫是不规范的但是总的来说都可以看做是在数据区的,可以很直观的认为程序运行所需要的数据是存储在数据区的,但是事实是并不是所有的数据都是在数据区中存储的。数据区中存储都是持久化的存储对象,也就是说这次使用这个数据后需要保留对它这次操作的结果。
但是在C程序里面,变量在定义要么是在函数外面,要么是在函数里面,main函数也好,用户自定义的函数也好,都服从这个规律。编译器对代码编译之后把你所定义的变量放在哪里是要看你定义的变量在哪里定义的、长的什么样子了。在哪里定义的?一般要么是在函数外面,要么是在函数内部(这里的内部包含了函数参数列表);长的什么样子呢?也就是说除了简单的类似 “int a; “这样的,还有没有其他的关键字修饰你定义的变量了,例如static 等。
分清变量的存储位置和存储类型,将对我们理解C语言里其他的东西很有用。下面逐条分析一下吧。
1、 在函数外面定义的变量,顾名思义:外部变量(extern variable)。有时候前面会多出来个extern ,如“extern int a ;“,extern这个关键字只不过是扩大了变量的作用域范围,对外部变量的存储位置是没有影响的。一般说来,不管前面是否有extern,你在函数外面(包括main函数)定义的变量都是放在程序的数据区中的固定位置的。
2、 在函数内部定义的变量,顾名思义:内部变量(internal variable)。一般有人会认为内部变量是放置在程序的堆栈里,这种说法不算错,但是只说对了一半。内部变量有两种:一种是静态的内部变量,一种是动态的。静态的是在定义时有个关键字static出现。而动态的则没有。例如:
void fun(int arg)
{
int a ;
static int b;
}
很明显,a是动态的,b是静态的。
静态的内部变量的存储位置和外部变量是一样的。而动态变量的存储位置是在调用函数时所使用的堆栈中,通常对函数内部的动态变量的存取就是在堆栈中操作。
这里举例没有讲到参数列表里面的变量arg ,其实arg 的位置是和动态变量一样的。
用一个示例说明一下上面两点。程序的编译环境是VC6.0.
#include<stdio.h>
/*
*file=ex1.c
*date =08.6.2
*/
/*定义外部变量*/
int ext=5;
/*定义函数*/
void fun(int arg)
{
int a=1; /* a 是动态内部变量*/
static int b=1;/* b 是静态内部变量*/
a+=arg;
b+=arg;
printf("a = %d/nb = %d/n",a,b);
}
int main(int argc,char ** argv)
{
fun(ext);/*第一次调用fun*/
fun(ext);/*第二次调用fun*/
return 0;
}
这个程序的结果只要是有点C经验的都会知道是什么。第一次调用fun函数输出的是 a = 6 b = 6
第二次调用fun函数输出的是 a = 6 b = 11
但是我要用编译生成的会变代码解释一遍相信大家会更清楚,因为我就是看了汇编后的代码对这个才有了更清楚的认识。汇编后的代码可以在你调试程序时的反汇编窗口里面看到。
下面只是选取了反汇编窗口里面最主要的一部分来进行说明,用C++的注释风格进行了注释说明,下面最左边的一列是程序代码的二进制地址。右边对应的就是在这些地址中放的指令。
如果对intel汇编熟悉那理解起来就很容易了。
@ILT+0(_fun): //fun函数在汇编后名字变为@ILT+0
00401005 jmp fun (00401020)
@ILT+5(_main): //main函数在汇编后名字变为@ILT+5
0040100A jmp main (004010a0)//整个程序的入口地址是0040100A
//从PE文件头中的信息可以知道这个地址
0040100F int 3
00401010 int 3
00401011 int 3
00401012 int 3
00401013 int 3
00401014 int 3
00401015 int 3
00401016 int 3
00401017 int 3
00401018 int 3
00401019 int 3
0040101A int 3
0040101B int 3
0040101C int 3
0040101D int 3
0040101E int 3
0040101F int 3
--- c:/ex1.c -----------------------------------------------------------------------
1: #include<stdio.h>
2: /*
3: *file=ex1.c
4: *date =08.6.2
5: */
6: /*定义外部变量*/
7: int ext=5;
8: /*定义函数*/
9: void fun(int arg)
10: {
00401020 push ebp //下面这几行代码跟main函数刚开始时差不多,不
//过fun函数为局部变量留了0x44H个字节的空间。
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]
0040102C mov ecx,11h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
11: int a=1; /* a 是动态内部变量*/
00401038 mov dword ptr [ebp-4],1 //动态内部变量就放在堆栈的这个地方。
12: static int b=1;/* b 是静态内部变量*/
13: a+=arg;
0040103F mov eax,dword ptr [ebp-4] //把a放在eax寄存器中
00401042 add eax,dword ptr [ebp+8] //ebp+8地址放的是什么呢?,看看在
//004010BD这个地址是什么指令吧,
//其实这时的ebp+8地址中放的就是传
//入的fun的实参:ext=5,指令结束后
//eax就是6了。如果你对堆栈的使用
//有疑问,可以自己跟着程序的执行画
//一下堆栈的使用情况。
00401045 mov dword ptr [ebp-4],eax //eax的值(6)赋给a了。
14: b+=arg; //这条语句的执行跟上一条差不多,但
//是看看下条指令我们就知道b不是放
//在堆栈中了。
00401048 mov ecx,dword ptr [b (00424a34)]
//从这条指令我们可以看出来b 的地址是00424a34,看看004010B8处的这条指
//令:系统是从地址00424a30处取外部变
//量ext的值的,也就是说外部变量ext后
//面紧接着放着的就是静态内部变量b 。
0040104E add ecx,dword ptr [ebp+8]
00401051 mov dword ptr [b (00424a34)],ecx //这条指令执行后,b=6,也
//即00424a34地址处的内容是6,
//当我们第二次调用fun函数
//后时,你也就理解了b为什么
//变成了11
15: printf("a = %d/nb = %d/n",a,b); //下面的是调用printf函数输出
00401057 mov edx,dword ptr [b (00424a34)]
0040105D push edx
0040105E mov eax,dword ptr [ebp-4]
00401061 push eax
00401062 push offset string "a = %d/nb = %d/n" (0042201c)
00401067 call printf (00401100)
0040106C add esp,0Ch
16: }
0040106F pop edi
00401070 pop esi
00401071 pop ebx
00401072 add esp,44h
00401075 cmp ebp,esp
00401077 call __chkesp (00401180) //检查堆栈
0040107C mov esp,ebp
0040107E pop ebp
0040107F ret //fun函数是在这里返回的。
--- No source file -----------------------------------------------------------------
00401080 int 3
00401081 int 3
00401082 int 3
00401083 int 3
00401084 int 3
00401085 int 3
00401086 int 3
00401087 int 3
00401088 int 3
00401089 int 3
0040108A int 3
0040108B int 3
0040108C int 3
0040108D int 3
0040108E int 3
0040108F int 3
00401090 int 3
00401091 int 3
00401092 int 3
00401093 int 3
00401094 int 3
00401095 int 3
00401096 int 3
00401097 int 3
00401098 int 3
00401099 int 3
0040109A int 3
0040109B int 3
0040109C int 3
0040109D int 3
0040109E int 3
0040109F int 3
--- c:/ex1.c -----------------------------------------------------------------------
17: int main(int argc,char ** argv)
18: {
004010A0 push ebp //保存ebp寄存器的内容,下面要用到这个寄存器
//保存esp的内容,堆栈中用ebp来寻址,所以要
//保存
004010A1 mov ebp,esp
004010A3 sub esp,40h //这里esp减去0x40H,是给局部变量留的空间
004010A6 push ebx
004010A7 push esi
004010A8 push edi
004010A9 lea edi,[ebp-40h] //ebp的值就是esp减去0x40H之前的值
004010AC mov ecx,10h
004010B1 mov eax,0CCCCCCCCh
004010B6 rep stos dword ptr [edi] //刚才给局部变量留了0x40H(64)个字节的空间,这
//里都初始化为0xCCCCCCCCH,是按字初始化的,
//一共0x10H(16)个字.这段区域的值可以写程序用指
//针来查看一下。
19: fun(ext);/*第一次调用fun*/
004010B8 mov eax,[_ext (00424a30)]//ext是我们定义的一个外部变量,它的位
//置是固定的,这个指令是把ext的值放
//入 eax寄存器中。
004010BD push eax //调用fun函数需要一个实参,这个实参的传
//入我们可以看到是通过把它压入到堆栈中
//传递的
004010BE call @ILT+0(_fun) (00401005)//这里就调用了函数@ILT+0,也就是fun,
//现在跟着看看fun函数都是怎么处理的
004010C3 add esp,4
20: fun(ext);/*第二次调用fun*/
004010C6 mov ecx,dword ptr [_ext (00424a30)]//这次调用fun函数是用ecx传参
004010CC push ecx
004010CD call @ILT+0(_fun) (00401005)
004010D2 add esp,4
21: return 0;
004010D5 xor eax,eax
22: }
004010D7 pop edi
004010D8 pop esi
004010D9 pop ebx
004010DA add esp,40h
004010DD cmp ebp,esp
004010DF call __chkesp (00401180)
004010E4 mov esp,ebp
004010E6 pop ebp
004010E7 ret
代码实在是比较长,不知道能否忍着看完,主要是为了说明以上两点:简单的说除了外部变量和静态内部变量之外,其它的变量都没有在程序的静态存储区中存放,需要这些变量的时候是把它们放在堆栈中的。