用汇编语言研究C语言的全局变量、局部变量、参数、返回值放在哪里
前提知识
c0s调用main函数的地址: 11ah
main函数的连接地址: 01fah
一、全局变量与局部变量
测试程序
int a1,a2,a3; void f(void);
void g(void);
void h(void);
main()
{
int b1,b2,b3;
a1 = 0xa1;a2 = 0xa2;a3 = 0xa3;
b1 = 0xb1;b2 = 0xb2;b3 = 0xb3;
} void f(void)
{
int c1,c2,c3;
a1 = 0x0fa1;a2 = 0x0fa2; a3 = 0x0fa3;
c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
c1 = c2 + c3;
} void g(void)
{
int i = ;
while(i--);
} void h(void)
{
int h1,h2,h3,h4,h5,h6,h7;
h1 = 0xc1; h2 = 0xc2; h3 = 0xc3;h4 = 0xc4;
h5 = 0xc5; h6 = 0xc6; h7 = 0xc7;
h1 = h2 + h3;
h2 = h3 + h4;
}
编译、连接后,用debug调试这段代码,根据函数分别贴出对应的反汇编代码
1、main函数
(1)全局变量
main()
{
int b1,b2,b3;
a1 = 0xa1;a2 = 0xa2;a3 = 0xa3;
b1 = 0xb1;b2 = 0xb2;b3 = 0xb3;
}
对应的反汇编代码
可以看到全局变量,a1、a2、a3的地址分别是ds:[01a6]、ds:[01a8]、ds:[01aa]。
可以看到,ds:[01a6]的物理地址是16266h,而程序的结束位置是CS:[2a0]的物理地址是15d60。可见,全局变量位于代码段外。ds=ss,而sp=ffe6,ss:sp的物理位置为260a6h,即栈顶位于260a6h,栈应高于栈顶。所以全局变量不可能位于栈区。
综上所述,我认为全局变量位于非代码段,非栈段,而位于data段(初始化)或者bss段(未初始化)。
(2)局部变量
开辟在栈中的局部变量
a) 编译器先将BP压入栈
b)用BP保存栈指针,然后SP-6,为局部变量开辟空间。
push bp
mov bp,sp
sub sp,+6
c) 函数返回前恢复栈,释放局部变量空间
mov sp,bp
d) 恢复BP
2、f函数
void f(void)
{
int c1,c2,c3;
a1 = 0x0fa1;a2 = 0x0fa2; a3 = 0x0fa3;
c1 = 0xc1; c2 = 0xc2; c3 = 0xc3;
c1 = c2 + c3;
}
对应的反汇编代码
局部变量c1、c2被开辟在寄存器SI、DI中,而c3则开辟在栈中。
开辟在寄存器中的局部变量
a) 编译器先将BP压入栈
b)用BP保存栈指针,将SI、DI压入栈,从而为局部变量开辟空间
push bp
mov bp,sp
push si
push di
c) 函数返回前恢复寄存器(释放局部变量),然后恢复栈
pop di
pop si
mov sp,bp
d) 恢复BP
无论开辟到栈中,还是开辟在寄存器中,栈指针的移动都是相同的。
3、g函数
void g(void)
{
int i = ;
while(i--);
}
对应的反汇编代码
通过这个函数的反汇编可以重复验证“开辟在寄存器中的局部变量”的结论。
4、h函数
还会开辟到其他地方吗?
void h(void)
{
int h1,h2,h3,h4,h5,h6,h7;
h1 = 0xc1; h2 = 0xc2; h3 = 0xc3;h4 = 0xc4;
h5 = 0xc5; h6 = 0xc6; h7 = 0xc7;
h1 = h2 + h3;
h2 = h3 + h4;
}
对应的反汇编代码
可以看到,当有7个局部变量的情况下,局部变量除了开辟在栈中,就是寄存器中,没别的地方了。
什么时候开辟在栈中,什么时候开辟在寄存器中?这个问题还搞不懂,不过感觉没多大意义。
二、函数如何传递参数,又是如何接受的呢
测试程序
void showchar(char a,char b); main()
{
showchar('a',);
} void showchar(char a,char b)
{
char r;
r = a;
r = b;
}
1、main函数
main()
{
showchar('a',);
}
对应的反汇编代码
a) 函数的参数是通过栈传递,而且是从右到左依次入栈
b) 即使是char型变量,在传递参数时,也是占用两个字节,因为push操作是两个字节为单位的。取的时候,是按照它的类型来的
c) 释放参数可以通过多次pop来实现。事实上,有时是通过“add sp,+数值”来实现的。显然,后者释放空间来的更快。
2、showchar函数
void showchar(char a,char b)
{
char r;
r = a;
r = b;
}
对应的反汇编代码
d) 调用函数前后堆栈保持一致,也就是函数返回时要让堆栈指针恢复到和进入函数时一样的状态
e) 函数接受形参是通过从栈中取的
f) 对于为初始化的局部变量,编译器是不会给它赋初值的,而是拿来就用
g) main函数的局部变量的寿命要比调用函数showchar的形参寿命长
h) BP不仅保存了堆栈的值,而且通过它可以找到传入参数的值,BP+4是第一个参数,BP+6是第二个参数......取参数是从左到右取的(这就是堆栈的妙处)
三、函数返回值
测试程序
char f(void); main()
{
char c;
c = f();
} char f(void)
{
return 'a';
}
1、main函数
char f(void); main()
{
char c;
c = f();
}
对应的反汇编代码
2、f函数
char f(void)
{
return 'a';
}
对应的反汇编代码
函数返回值:
char型 AL
int型 AX
四、总结
1、全局变量
全局变量位于非代码段,非栈段,而位于data段(初始化)或者bss段(未初始化)。
2、局部变量
a、开辟在堆栈中的局部变量,通过“mov sp,bp”来释放空间
b、开辟在寄存器中的局部变量(push si/di),通过"pop si/di”来实现
c、未初始化的局部变量,编译器并不会给它赋初值,而是拿来就用
3、如何传递参数(主函数)
a、函数的参数是通过栈传递,而且是从右到左依次入栈
b、即使是char型变量,在传递参数时,也是占用两个字节,因为push操作是两个字节为单位的。
c、showchar('a',2)这样的传入两个常数,也会在堆栈中开辟两个空间,也即对应两个实参变量。
4、函数如何接收参数(子函数)
a、 函数接受形参是通过从栈中取的
b、通过BP可以找到传入参数的值,BP+4是第一个参数,BP+6是第二个参数......取参数是从左到右取的
5、如何释放参数(主函数)
释放参数可以通过多次pop来实现。事实上,有时是通过“add sp,+数值”来实现的。
6、函数返回值
char型 AL
int型 AX
五、其他结论
1、局部变量、传递参数和接收参数都与堆栈脱不了干系
一个结论:局部变量、传递参数和接收参数都伴随着入栈、出栈、读堆栈中的内容等操作。对堆栈的这些操作,完成了变量和参数的创建、使用和释放。
开辟在堆栈中的变量和参数都是在堆栈中开辟空间的,释放的时候也就通过移动sp或者pop指令来完成。
开辟在寄存器中的变量,是通过“push si/di”来获取空间的,释放的时候是通过“pop si/di”来完成的。
2、C语言函数汇编后的代码
push bp
mov bp,sp
...
...
mov sp,bp
pop bp
ret
这些代码是每个函数反汇编都会出现的,怎样理解呢?
a、BP保存了堆栈的值,所以可以利用堆栈来开辟局部变量的空间。(倘若不保存堆栈的值,将来怎样回收这些空间呢?可能会比较麻烦)
b、通过BP可以找到传入参数的值,BP+4是第一个参数,BP+6是第二个参数......取参数是从左到右取的(这就是堆栈的妙处)
《汇编语言》319页研究实验3 “使用内存空间”
用汇编语言研究C语言的全局变量、局部变量、参数、返回值放在哪里的更多相关文章
- day03 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数
本节内容 1. 函数基本语法及特性 2. 参数与局部变量 3. 返回值 嵌套函数 4.递归 5.匿名函数 6.函数式编程介绍 7.高阶函数 8.内置函数 温故知新 1. 集合 主要作用: 去重 关系测 ...
- Swift2.0语言教程之函数的返回值与函数类型
Swift2.0语言教程之函数的返回值与函数类型 Swift2.0中函数的返回值 根据是否具有返回值,函数可以分为无返回值函数和有返回值函数.以下将会对这两种函数类型进行讲解. Swift2.0中具有 ...
- c语言 局部变量做返回值 问题
一般的来说,函数是可以返回局部变量的. 局部变量的作用域只在函数内部,在函数返回后,局部变量的内存已经释放了.因此,如果函数返回的是局部变量的值,不涉及地址,程序不会出错.但是如果返回的是局部变量的地 ...
- 关于C语言函数调用压栈和返回值问题的疑惑
按照C编译器的约定调用函数时压栈的顺序是从右向左,并且返回值是保存在eax寄存器当中.这个命题本该是成立的,下面用一个小程序来反汇编观察执行过程: #include<stdio.h> in ...
- C语言 realloc为什么要有返回值,realloc返回值具体解释/(解决随意长度字符串输入问题)。
在C语言操作中会用到大量的内存操作,当中非经常常使用的一个是realloc(). 由字面意思能够知道,该函数的作用是用于又一次分配内存. 使用方式例如以下: NewPtr=(数据类型*)realloc ...
- 013_go语言中的函数多返回值
代码演示 package main import "fmt" func vals() (int, int) { return 3, 7 } func main() { a, b : ...
- r语言 function 指定多个返回值
# Goals: To write functions # To write functions that send back multiple objects. # FIRST LEARN ABOU ...
- C语言中赋值表达式的返回值是什么?
我们或多或少都有过,或者见过将赋值表达式参与运算的情况.这通常会伴随着一些意想不到的问题.今天我就见到了一段奇怪的代码: #include<stdio.h> int main() { ; ...
- 深入研究C语言 第二篇(续)
1. 关于如下的程序,关于结构体的拷贝,拷贝是拷贝到内存中的什么地方? 我们进入debug进行反汇编,单步等操作跟踪查看.发现: 在main中,我们看到call 0266应该对应的是转跳到func处执 ...
随机推荐
- linux 监控命令
先总结下常用的一些监控工具: ##linux命令 w 系统负载 lsof -p pid 进程打开的文件 lsof -i:port 端口的运行情况 free -m 内存情况 vmstat 进程.内存.内 ...
- druid报异常 “sql injection violation, part alway true condition not allow”的解决方案
使用durid连接池组件,执行sql时发现异常如下: Caused by: java.sql.SQLException: sql injection violation, part alway tru ...
- mysql创建数据库(指定编码)
如下脚本创建数据库yourdbname,并制定默认的字符集是utf8. CREATE DATABASE IF NOT EXISTS yourdbname DEFAULT CHARSET utf8 CO ...
- UVaLive6039 Uva1668 Let's Go Green
一开始考虑所有边都是单独的一条路径 然后尽量多的合并 #include<cstdio> #include<cstring> #include<cstdlib> #i ...
- JAVA去掉字符串前面的0
最佳方案:使用正则 String str = "000000001234034120"; String newStr = str.replaceAll("^(0+)&qu ...
- 【Linux常用工具】1.1 diff命令的三种格式
diff是用来比较两个文本文件的差异的工具,它有三种格式,下面用实例介绍一下: 准备三个测试文件1.txt 2.txt 3.txt bixiaopeng@bixiaopengtekiMacBook-P ...
- xp系统
产品密钥 MRX3F-47B9T-2487J-KWKMF-RPWBY(亲测可用)
- [转] 考验你的JavaScript底细
http://sentsin.com/ 尽管今日的JavaScript已经突飞猛进,但JS的许多特性仍然保留,以下题目并不是有意设坑,许多地方将验证你的JS底细,如果错了一半,请别告诉我你从事前端. ...
- 学习微信小程序之css15解决父盒子高度塌陷
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 偶遇问题 - - JavaScript 取消链接默认行为问题
今天在测试<JavaScript DOM编程艺术(第2版)>中第69页代码时,遇到了问题.本来预期效果应该是点击链接后不跳转当前页面,而是另外弹出有个窗口.但结果却是页面跳转了.代码如下图 ...