本节必须掌握的知识点:
掌握局部变量、全局变量存放在哪
熟练画堆栈图
掌握每个函数从哪开始被调用的,从哪结束的
开始看本节前,请读者思考如下几问题:
- 局部变量存放在哪里?
- 全局变量存放在哪里?
- 编译器是怎么辨别哪个是全局变量,哪个是局部变量?
- 每个函数都有生命周期吗?
- 函数内嵌入函数调用是怎么运作的?
爱思考、爱动手的读者在学习前面的章节中应该已经知道了以上五个问题的答案。本节将再次剖析这些问题,强化对上述问题的理解。
9.5.1 局部变量存放在哪里?
分三种情况讨论,第一种情况:main函数内只有一个局部变量并没有调用函数的例子。
实验七十九:main函数内的局部变量
在VS中新建项目9-5-1.c:
/*
main函数内的局部变量
*/
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int i = 0; //局部变量
return 0;
}
●代码解析:
在main函数里定义了一个int类型的局部变量i,我们知道在函数内定义的变量我们统称为局部变量,main函数内定义的变量也不例外。
局部变量存放在栈中,只有系统调用变量所在函数时,系统会动态在函数栈内分配临时空间给局部变量。
这是最简洁的程序了,我们看它的反汇编,来看它是存放在哪里的。
●Debug版本反汇编代码:
int main(void) {
008516F0 push ebp
008516F1 mov ebp,esp ;建立堆栈框架
008516F3 sub esp,0CCh ;分配0CCH个字节的栈局部变量内空间
008516F9 push ebx
008516FA push esi 保护寄存器入栈
008516FB push edi
008516FC lea edi,[ebp-0CCh]
00851702 mov ecx,33h
00851707 mov eax,0CCCCCCCCh 堆栈空间初始化0xcc
0085170C rep stos dword ptr es:[edi]
0085170E mov ecx,offset _68E6568B_9-5-1@c (085B003h)
00851713 call @__CheckForDebuggerJustMyCode@4 (0851203h) 堆栈校验
int i = 0; //局部变量
00851718 mov dword ptr [i],0 ;栈内局部变量i初始化为0
查看局部变量i的地址和值:
在VS监视窗口输入&i,监视窗口显示如下:
名称 | 值 | 类型 | |
▶ | &i | 0x00f3fae0 {0} | int * |
打开寄存器窗口,查看ebp寄存器的值为0x 012FF9B8,局部变量i的地址为0x012FF9B0,因此局部变量i=[ebp-8]。
【注】这是VS中Debug版本的反汇编代码,与其他版本或DtDebug调试器中的栈内地址可能会稍有差异,例如i=[ebp-4]。
在内存窗口地址栏输入0x012FF9B0:
图9-20 局部变量i
return 0;
0085171F xor eax,eax ;返回值0
}
00851721 pop edi
00851722 pop esi 恢复栈内寄存器
00851723 pop ebx
00851724 add esp,0CCh ;释放栈内局部变量空间
0085172A cmp ebp,esp
0085172C call __RTC_CheckEsp (085120Dh) 堆栈校验
00851731 mov esp,ebp
00851733 pop ebp 释放堆栈框架
00851734 ret
●堆栈图:
图9-21 main函数栈
【注】反汇编代码中的add esp,0cch语句和mov esp,ebp语句等价。请读者参考上述堆栈图,根据自己的编译器或调试器,绘制自己本机的堆栈图。
实验八十:main函数调用函数的情况
在VS中新建项目9-5-2.c:
/*
main函数调用函数的情况
*/
#include <stdio.h>
#include <stdlib.h>
int funtion()
{
int i = 2;
return i;
}
int main(void) {
int a = funtion();
printf("a = %d\n", a);
system("pause");
return 0;
}
●代码解析:
1、int a = funtion();//在main函数内定义了局部变量a,调用funtion函数,funtion函数返回值赋值给变量a;
2、进入到funtion函数,funtion函数里定义了局部变量i,并赋值;
3、return i;返回值是i,i的值为2,所以funtion函数返回值是2;
4、int a = 2;执行printf函数输出 a = 2;
●反汇编代码:
int main(void) {
01221880 push ebp
01221881 mov ebp,esp ;建立main函数堆栈框架
01221883 sub esp,0CCh ;分配局部变量空间
01221889 push ebx
0122188A push esi ;保护寄存器入栈
0122188B push edi
0122188C lea edi,[ebp-0CCh]
01221892 mov ecx,33h 初始化堆栈空间
01221897 mov eax,0CCCCCCCCh
0122189C rep stos dword ptr es:[edi]
0122189E mov ecx,offset _6AA0E8D2_9-5-2@c (0122C003h)
012218A3 call @__CheckForDebuggerJustMyCode@4 (0122121Ch) ;堆栈校验
int a = funtion();
012218A8 call _funtion (012211C2h) ;函数调用
012218AD mov dword ptr [a],eax ;函数返回值保存到局部变量a
printf("a = %d\n", a);
012218B0 mov eax,dword ptr [a]
012218B3 push eax
012218B4 push offset string "a = %d\n" (01227B30h)
012218B9 call _printf (0122104Bh) ;输出变量a
012218BE add esp,8
system("pause");
012218C1 mov esi,esp
012218C3 push offset string "pause" (01227B3Ch)
012218C8 call dword ptr [__imp__system (0122B168h)]
012218CE add esp,4
012218D1 cmp esi,esp
012218D3 call __RTC_CheckEsp (01221226h)
return 0;
012218D8 xor eax,eax
}
012218DA pop edi
012218DB pop esi ;保护寄存器出栈
012218DC pop ebx
012218DD add esp,0CCh ;释放sub分配的局部变量空间
}
012218E3 cmp ebp,esp
012218E5 call __RTC_CheckEsp (01221226h) ;堆栈校验
012218EA mov esp,ebp ;释放局部变量空间
012218EC pop ebp ;释放ebp
012218ED ret ;函数返回
int funtion()
{
01221820 push ebp
01221821 mov ebp,esp ;建立funtion函数堆栈框架
01221823 sub esp,0CCh ;分配局部变量空间
01221829 push ebx
0122182A push esi ;保护寄存器
0122182B push edi
0122182C lea edi,[ebp-0CCh]
01221832 mov ecx,33h
01221837 mov eax,0CCCCCCCCh ;初始化堆栈空间
0122183C rep stos dword ptr es:[edi]
0122183E mov ecx,offset _6AA0E8D2_9-5-2@c (0122C003h)
01221843 call @__CheckForDebuggerJustMyCode@4 (0122121Ch) ;堆栈校验
int i = 2;
01221848 mov dword ptr [i],2 ;局部变量i赋值
return i;
0122184F mov eax,dword ptr [i] ;eax保存返回值
}
01221852 pop edi
01221853 pop esi ;保护寄存器出栈
01221854 pop ebx
01221855 add esp,0CCh ;释放sub指令分配堆栈空间
0122185B cmp ebp,esp
0122185D call __RTC_CheckEsp (01221226h) ;堆栈校验
01221862 mov esp,ebp
01221864 pop ebp ;释放堆栈框架
01221865 ret ;函数返回
●堆栈图:
图9-22 main函数+funtion函数堆栈图
总结
每个函数都有他自己的生命周期,在实验八十中,main函数有自己的生命周期,funtion函数也有自己的生命周期。main函数的生命周期是程序结束,funtion函数的生命周期是执行到return i;结束funtion函数。
局部变量是存放在栈中的,局部变量的生命周期是所在函数调用结束,随着系统动态分配的空间也会释放掉。
实验八十一:main函数调用两个函数
在VS中新建项目9-5-3.c:
/*
main函数调用两个函数
*/
#include <stdio.h>
#include <stdlib.h>
int funtion2()
{
int j = 6;
return j;
}
int funtion()
{
int i = 2;
return i;
}
int main(void) {
int a = funtion();
int b = funtion2();
printf("a = %d\nb = %d\n", a, b);
system("pause");
return 0;
}
●代码解析:
1.int a = funtion();//在main函数内定义了局部变量a,调用funtion函数,funtion函数返回值赋值给变量a;
2.进入到funtion函数,funtion函数里定义了局部变量i,并赋值;
3.return i;返回值是i,i的值为2,所以funtion函数返回值是2;
4.int b = funtion2();//在main函数内定义了局部变量b,调用funtion2函数,funtion2函数返回值赋值给变量b;
5.进入到funtion2函数,funtion2函数里定义了局部变量j,并赋值;
6.return j;返回值是j,j的值为6,所以funtion2函数返回值是6;
7.执行printf函数,输出
a = 2
b = 6;
●反汇编代码:
int main(void) {
00B418F0 push ebp
00B418F1 mov ebp,esp
00B418F3 sub esp,0D8h
00B418F9 push ebx
00B418FA push esi
00B418FB push edi
00B418FC lea edi,[ebp-0D8h]
00B41902 mov ecx,36h
00B41907 mov eax,0CCCCCCCCh
00B4190C rep stos dword ptr es:[edi]
00B4190E mov ecx,offset _6B6282E5_9-5-3@c (0B4C003h)
00B41913 call @__CheckForDebuggerJustMyCode@4 (0B41221h)
int a = funtion();
00B41918 call _funtion (0B411C2h) ;函数调用
00B4191D mov dword ptr [a],eax
int b = funtion2();
00B41920 call _funtion2 (0B411F9h) ;函数调用
00B41925 mov dword ptr [b],eax
printf("a = %d\nb = %d\n", a, b);
00B41928 mov eax,dword ptr [b]
00B4192B push eax
00B4192C mov ecx,dword ptr [a]
00B4192F push ecx
00B41930 push offset string "a = %d\nb = %d\n" (0B47B30h)
00B41935 call _printf (0B4104Bh)
00B4193A add esp,0Ch
system("pause");
00B4193D mov esi,esp
00B4193F push offset string "pause" (0B47B44h)
00B41944 call dword ptr [__imp__system (0B4B168h)]
00B4194A add esp,4
00B4194D cmp esi,esp
00B4194F call __RTC_CheckEsp (0B4122Bh)
return 0;
00B41954 xor eax,eax
}
00B41956 pop edi
}
00B41957 pop esi
00B41958 pop ebx
00B41959 add esp,0D8h
00B4195F cmp ebp,esp
00B41961 call __RTC_CheckEsp (0B4122Bh)
00B41966 mov esp,ebp
00B41968 pop ebp
00B41969 ret
int funtion()
{
00B41830 push ebp
00B41831 mov ebp,esp
00B41833 sub esp,0CCh
00B41839 push ebx
00B4183A push esi
00B4183B push edi
00B4183C lea edi,[ebp-0CCh]
00B41842 mov ecx,33h
00B41847 mov eax,0CCCCCCCCh
00B4184C rep stos dword ptr es:[edi]
00B4184E mov ecx,offset _6B6282E5_9-5-3@c (0B4C003h)
00B41853 call @__CheckForDebuggerJustMyCode@4 (0B41221h)
int i = 2;
00B41858 mov dword ptr [i],2 ;局部变量i=2
return i;
00B4185F mov eax,dword ptr [i]
}
00B41862 pop edi
00B41863 pop esi
00B41864 pop ebx
00B41865 add esp,0CCh
00B4186B cmp ebp,esp
00B4186D call __RTC_CheckEsp (0B4122Bh)
00B41872 mov esp,ebp
00B41874 pop ebp
00B41875 ret
int funtion2()
{
00B41890 push ebp
00B41891 mov ebp,esp
00B41893 sub esp,0CCh
00B41899 push ebx
00B4189A push esi
00B4189B push edi
00B4189C lea edi,[ebp-0CCh]
00B418A2 mov ecx,33h
00B418A7 mov eax,0CCCCCCCCh
00B418AC rep stos dword ptr es:[edi]
00B418AE mov ecx,offset _6B6282E5_9-5-3@c (0B4C003h)
00B418B3 call @__CheckForDebuggerJustMyCode@4 (0B41221h)
int j = 6;
00B418B8 mov dword ptr [j],6 ;局部变量j=6
return j;
00B418BF mov eax,dword ptr [j]
}
00B418C2 pop edi
00B418C3 pop esi
00B418C4 pop ebx
00B418C5 add esp,0CCh
00B418CB cmp ebp,esp
00B418CD call __RTC_CheckEsp (0B4122Bh)
00B418D2 mov esp,ebp
00B418D4 pop ebp
00B418D5 ret
●堆栈图:
图9-23 main函数
图9-24 funtion函数
图9-25 funtion2函数
细心的读者可能会发现一个有意思的事情,funtion函数栈被释放后,紧接着这段堆栈空间就被分配给了funtion2函数。当然这并不代表这一定会发生这样的事情,也许是一个偶然事件。
总结
不管多么复杂的程序,只要我们慢慢画堆栈图,都会剥开云雾,都会明白它们之间干了什么,谁调用了谁,怎么调用的。每个函数都有生命周期,每个变量也有自己的生命周期,局部变量的生命周期,通过画堆栈图,我们可以很明确的看出每个变量的生命周期。
9.5.2 全局变量存放在哪里?
全局变量的作用域是从全局变量定义的位置到本源文件结束都有效。
我们先看一下全局变量在反汇编中是怎么体现的,如实验八十二示例示例代码9-5-4.c。
实验八十二:全局变量的作用域
在VS中新建项目9-5-4.c:
/*
全局变量的作用域
*/
#include <stdio.h>
#include <stdlib.h>
int i = 2;
int main(void) {
int j = i;
return 0;
}
VS中的反汇编代码:
/*
全局变量的作用域
*/
#include <stdio.h>
#include <stdlib.h>
int i = 2;
int main(void) {
010416F0 push ebp
010416F1 mov ebp,esp
010416F3 sub esp,0CCh
010416F9 push ebx
010416FA push esi
010416FB push edi
010416FC lea edi,[ebp-0CCh]
01041702 mov ecx,33h
01041707 mov eax,0CCCCCCCCh
0104170C rep stos dword ptr es:[edi]
0104170E mov ecx,offset _6E2D9460_9-5-4@c (0104B003h)
01041713 call @__CheckForDebuggerJustMyCode@4 (01041203h)
int j = i;
01041718 mov eax,dword ptr [i (01049000h)] ;全局变量i
0104171D mov dword ptr [j],eax ;局部变量j
return 0;
01041720 xor eax,eax
}
01041722 pop edi
01041723 pop esi
01041724 pop ebx
01041725 add esp,0CCh
0104172B cmp ebp,esp
0104172D call __RTC_CheckEsp (0104120Dh)
01041732 mov esp,ebp
01041734 pop ebp
01041735 ret
01041718 mov eax,dword ptr [i (01049000h)] ;全局变量i
这一行中(01049000h)正是全局变量i的存放地址。
全局变量编译的时候就已经确定了内存地址和宽度,变量名就是内存地址的别名。如果不重新编译(也就是不重新构建程序),全局变量的内存地址将不会改变。
总结
全局变量保存在内存的全局区中,占用静态的存储单元。说到静态的存储单元,这里还要提一下全局变量分为:全局变量和静态全局变量。静态全局变量只是在int i = 2;前加static关键字。
书写形式:static int i =2;
全局变量与静态全局变量有什么区别?
全局变量作用范围:从全局变量定义的位置到本源文件结束都有效,如果想在别的文件中访问可以加上extern声明,书写形式:extend int i = 2;。
静态全局变量作用范围:只在定义它的文件中可用,而文件之外是不可以被看见的。静态全局变量就是用来解决重名问题的,使用静态全局变量就是告诉编译器这个变量只在当前文件使用,在别的文件中就不可以使用。如果静态全局变量定义在函数内,则它的作用域会被限制在该函数内。
对于一个完整的程序:内存分布有如下几个区、栈区、堆区、全局区、常量区、代码区。
图9-26 C语言内存分区
图9-26中有几个内存区域没有介绍到,以后的章节中会补充说明。接下来我们使用示例来说明C语言的内存区域。
实验八十三:C语言的内存区域
在VS中新建项目9-5-5.c:
/*
C语言的内存区域
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//全局区
int g_n1 = 1;//全局初始化区
char g_c2;//全局未初始化区
void funtion()
{
int a = 1;//funtion函数栈区
}
int main(void) {
int nNum = 1;//main函数栈区
char cStr2[] = "123";//main函数栈区
char *cStr1 = "hello";//cStr1在main函数栈区,hello\0在常量区
static int nNum1 = 0;//全局初始化区
char *pCStr = (char *)malloc(10);//分配10字节区域在堆区
strcpy_s(pCStr,10, "666");//666放在常量区
printf("程序代码区的地址\n");
printf("funtion=%08p\n", funtion);
printf("文字常量区 常量的地址\n");
printf("&cStr1=%08p\n", &cStr1);
printf("&pCStr=%08p\n", &pCStr);
printf("全局区变量的地址\n");
printf("&g_n1=%08p\n", &g_n1);
printf("&g_c2=%08p\n", &g_c2);
printf("&nNum1=%08p\n", &nNum1);
printf("栈区 变量的地址\n");
printf("&nNum=%08p\n", &nNum);
printf("&cStr2=%08p\n", &cStr2);
printf("堆区 空间的地址\n");
printf("pCStr=%08p\n", pCStr);
free(pCStr);//释放
system("pause");
return 0;
}
●输出结果:
程序代码区的地址
funtion=003B11C2
文字常量区 常量的地址
&cStr1=00EFFE0C
&pCStr=00EFFE00
全局区变量的地址
&g_n1=003BA000
&g_c2=003BA145
&nNum1=003BA140
栈区 变量的地址
&nNum=00EFFE24
&cStr2=00EFFE18
堆区 空间的地址
pCStr=03394A90
请按任意键继续. . .
9.5.3 全局变量引发的事故
在C语言中要求程序员尽量遵循最低权限原则,能使用局部变量就不要使用全局变量,不希望被修改的地址变量,形参就需要添加const修饰词。这些是善意的提醒。而在汇编语言中没有这样的要求,这需要程序员自己对代码的安全性负责。
因为全局变量的作用域是整个文件,在源文件的任意位置修改了全局变量的值,都会影响到其他地方对该全局变量的使用。对于更为复杂的多模块、多线程的程序则更要加倍小心。
【注】多线程的程序我们暂且不涉及,本书只是针对初学者编写,有兴趣的读者可以参考编程达人系列教程《Windows API每日一练》这本书。
接下来我们举例说明。
实验八十四:全局变量引发的事故
在VS中新建项目9-5-6.c:
/*
全局变量引发的事故
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int flag = 11111111;
int main(void)
{
if (flag == 11111111)
{
while (1)
{
Sleep(50);
printf("flag=%d\n\n", flag);
}
}
else if (flag == 22222222)
{
while (1)
{
Sleep(50);
printf("flag=%d\n\n", flag);
}
}
else if (flag == 33333333)
{
while (1)
{
Sleep(50);
printf("flag=%d\n\n", flag);
}
}
else
{
printf("flag=%d\n\n", flag);
}
system("pause");
return 0;
}
●输出结果:
flag=11111111
flag=11111111
flag=11111111
…
程序中,if/else语句块分别设置了3个while语句死循环结构,由flag全局变量的条件语句控制,全局变量编译的时候就已经确定了内存地址和宽度,并且存在于整个程序的生命周期。
程序运行期间,无法改变全局变量flag的值,我们将借助一款内存修改工具CE(编程达人网站资料下载CheatEngine)在程序运行时,通过直接修改内存的方式改变flag全局变量的值实现条件语句的跳转。
第一步:运行程序MyProjectOne.exe,打开CE,点击选择进程按钮,如图9-27所示。
第二步:选择打开的进程MyProjectOne.exe,点击open,如图9-28所示。
第三步:搜索框内输入搜索精确值“11111111”,点击首次搜索,左侧栏显示搜索值所在的内存地址,鼠标左键双机该地址,底栏描述信息栏显示地址“000DA000”处的值为“11111111”,如图9-29所示。
第四步:鼠标双机描述栏搜索值“11111111”,弹出的对话框中将其修改为“22222222”,如图9-30所示。
此时观察,程序运行的控制台窗口,如图9-31所示。
图9-31 变化的控制台窗口
图9-27 CE内存修改器
图9-28 打开进程MyProjectOne.exe
图9-29 搜索精确值的内存地址
图9-30 修改内存地址存储的值
我们发现运行结果发生了改变,这就是传说中的“基址”,只要找到了“基址”,我们就可以暴力施加一些小特技。
以上就是所为的全局变量引发的小事故,感兴趣的同学可以试着CE工具找一下类似于小游戏植物大战僵尸的“基址”,增强一下对学习编程的兴趣。
9.5.4 函数的参数内存分布情况
看下面这段代码,我们通过切换到反汇编,调出寄存器窗口,然后一步步画堆栈图的形式,让大家对函数的参数内存分布情况一目了然。
实验八十五:函数的参数内存分布情况
在VS中新建项目9-5-7.c:
/*
函数的参数内存分布情况
*/
#include <stdio.h>
#include <stdlib.h>
int fnAdd(int x, int y)
{
return x + y;
}
int main(void)
{
int i = fnAdd(1, 2);
return 0;
}
VS Debug版反汇编代码:
int main(void)
{
00F71760 push ebp
00F71761 mov ebp,esp ;建立堆栈框架
00F71763 sub esp,0CCh ;分配局部变量空间
00F71769 push ebx
00F7176A push esi ;保护寄存器入栈
00F7176B push edi
00F7176C lea edi,[ebp-0CCh]
00F71772 mov ecx,33h
00F71777 mov eax,0CCCCCCCCh 初始化堆栈
00F7177C rep stos dword ptr es:[edi]
00F7177E mov ecx,offset _6C6B2A39_9-5-7@c (0F7C003h)
00F71783 call @__CheckForDebuggerJustMyCode@4 (0F71208h) ;堆栈校验
int i = fnAdd(1, 2);
00F71788 push 2 ;参数2入栈
00F7178A push 1 ;参数1入栈
00F7178C call _fnAdd (0F711C2h) ;函数调用
00F71791 add esp,8 ;堆栈平衡
00F71794 mov dword ptr [i],eax ;函数返回值存入局部变量i
return 0;
00F71797 xor eax,eax
}
00F71799 pop edi
00F7179A pop esi ;恢复保护寄存器
00F7179B pop ebx
00F7179C add esp,0CCh ;释放局部变量堆栈空间
00F717A2 cmp ebp,esp
00F717A4 call __RTC_CheckEsp (0F71212h) ;堆栈校验
00F717A9 mov esp,ebp ;释放ebp
00F717AB pop ebp ;释放堆栈框架
00F717AC ret ;函数调用返回
int fnAdd(int x, int y)
{
00F71700 push ebp
00F71701 mov ebp,esp ;建立堆栈框架
00F71703 sub esp,0C0h ;分配局部变量空间
00F71709 push ebx
00F7170A push esi ;保护寄存器入栈
00F7170B push edi
00F7170C lea edi,[ebp-0C0h]
00F71712 mov ecx,30h
00F71717 mov eax,0CCCCCCCCh 初始化堆栈
00F7171C rep stos dword ptr es:[edi]
00F7171E mov ecx,offset _6C6B2A39_9-5-7@c (0F7C003h)
00F71723 call @__CheckForDebuggerJustMyCode@4 (0F71208h) ;堆栈校验
return x + y;
00F71728 mov eax,dword ptr [x] ;取变量x的值
00F7172B add eax,dword ptr [y] ;x+y
}
00F7172E pop edi
00F7172F pop esi ;恢复保护寄存器
00F71730 pop ebx
00F71731 add esp,0C0h ;释放局部变量堆栈空间
00F71737 cmp ebp,esp
00F71739 call __RTC_CheckEsp (0F71212h) ;堆栈校验
00F7173E mov esp,ebp ;释放ebp
00F71740 pop ebp ;释放堆栈框架
00F71741 ret ;函数调用返回
堆栈图:
图9-32 fnAdd函数堆栈图
最后执行完都回到最初开始的地方,如果没有回到最初开始地方,说明堆栈没平衡,程序出现Bug了。
重点:[ebp+8]是压入的第一个参数:1,[ebp+0xC]是压入的第二个参数:2,……而[ebp+4]是函数返回地址。
从[ebp+8]开始是参数存储的地方。[ebp+4]是函数返回地址。这里不要嫌弃笔者重复叙述,重要的事情说三遍。是为了能够让读者朋友加深印象。如果你没有画堆栈图,你会后悔的。
练习
1、写出下面程序运行结果,并画出堆栈图。
#include <stdio.h>
#include <windows.h>
int fnSub(int x,int y)
{
return x-y;
}
int fnAdd(int x,int y)
{
return x+y;
fnSub();
}
int main(void)
{
int i =fnAdd(1,2);
return 0;
}
2、写出下面程序运行结果,并画出堆栈图。
#include <stdio.h>
#include <windows.h>
int i = 2;
int fnAdd(int x,int y)
{
return x+y;
}
int main(void)
{
int j =fnAdd(1,2);
i = j;
printf(“%d\n”,i);
return 0;
}
3、思考题1:会出现什么结果,写出程序输出结果,并画出堆栈图。
int add(int a)
{
return add(a-1);
}
void main()
{
int i = add(5);
return;
}
4、思考题2:下面程序有问题吗?为什么?请写出理由。(提示数组下标是否越界?数组下标越界是否可用?自己可以切换到反汇编查看)。
#include <stdio.h>
#include <windows.h>
void fnStr()
{
printf(“Hello World”);
getchar();
}
void fnArr()
{
int arr[5]={1,2,3,4,5};
arr[6] = (int)fnStr;
}
int main(void)
{
fnArr();
return 0;
}
本文摘自编程达人系列教材《汇编的角度——C语言》。