社区
C语言
帖子详情
函数参数传递~~~~~~~
wq06100610
2004-04-08 01:50:05
请问~~在C/C++中,函数参数的传递到底是什么原理??比如入战出战,参数放在什么区域~能详细点举个例子吗???
...全文
393
7
打赏
收藏
函数参数传递~~~~~~~
请问~~在C/C++中,函数参数的传递到底是什么原理??比如入战出战,参数放在什么区域~能详细点举个例子吗???
复制链接
扫一扫
分享
转发到动态
举报
写回复
配置赞助广告
用AI写文章
7 条
回复
切换为时间正序
请发表友善的回复…
发表回复
打赏红包
vconan
2004-04-08
打赏
举报
回复
呵呵,看起来好复杂的
wuthering
2004-04-08
打赏
举报
回复
讲得真是好啊,学习
积木
2004-04-08
打赏
举报
回复
看看这篇文章应该对你有好处……
积木
2004-04-08
打赏
举报
回复
VC++就是好,还在难懂的汇编语句前加入了C语言的源代码。不过同时也有不少我
们不需要的代码。因此,你只需要关心红色的部分就可以了。 奇怪阿?不是参数
都用push传递了吗?怎么没看到被pop出来?问题其实是这样,当你调用Call进入
函数的时候Call背着你做了一件事。call把它下一条语句的地址push进了堆栈。(
旁人: 什么!这是为什么?)原因很简单,因为函数调用完了,要用ret返回。而
ret怎么知道返回哪里呢?对了, ret指令pop了call指令push给他的地址(搞清楚这
个关系哦),然后返回到了这个地址。call和ret配合的如此绝妙,一个PUSH一个
POP肯定不会让堆栈不平衡的(老外叫no stack unwinding)。现在明白了,如果你
来个pop eax,那eax里面是什么?当然是ret要用的返回地址了。好啦,你要是
pop eax就等于抢了ret要用的东西了。不论曾程序流程和道德标准上你做的都不对
:-P。 可是怎么在函数体里使用参数呢?问题其实并不难,既然参数在堆栈里我
们就可以使用esp(堆栈指针)来访问了。不过,我相信你也想到了。esp是个经常变
化的值。一旦,函数里出现pop或push他就会变化。这样很不容易定位参数的于内
存中的位置。因此,我们需要一个不会变化的东西作为访问参数的基准。看看函数
体的开头部分:
00401000 push ebp
00401001 mov ebp,esp
先用push ebp保存了原来ebp的值再把esp的值给ebp。原来ebp就是用来做基准的。
也难怪他被称为ebp(Base Pointer)。很自然ret返回前的pop ebp就是恢复原来
ebp的数值喽。当然一定要恢复,因为函数里也可以调用函数嘛。每个函数都用
ebp,自然要保证使用完后完璧归赵了。现在当函数执行到 mov ebp, esp后堆栈应
该变成这个样子了。
/-------------------\ Higher Address
| 参数2: 0x1100h |
+-----------------+
| 参数1: 0x8899h |
+-----------------+
| 函数返回地址 |
| 0x00401087 |
+-----------------+
| ebp |
\-------------------/ Lower Address <== stack pointer
& ebp all point to here, now
由于我们在VC++上使用的int类型是一个32位类型,ebp和函数返回值也是32位的。
因此每个量要占去4个字节。另外还需要注意堆栈的扩展方向是高地址到低地址。
有了这些指示。我们就可以分析出,第一个参数的地址是ebp + 08h,第二个参数
就是ebp + 0ch。看看反汇编的代码:
2: a = 0x4455;
00401018 mov dword ptr [ebp+8],4455h
3: b = 0x6677;
0040101F mov dword ptr [ebp+0Ch],6677h
与我们的计算吻合。之后呢:
00401031 pop ebp
00401032 ret
将ebp原来的数值完璧归赵,调用ret指令,ret指令pop出返回地址,之后返回到调
用函数的call指令的下一条语句。ret之后,堆栈应该变成这个样子了
/-------------------\ Higher Address
| 参数2: 0x1100h |
+-----------------+
| 参数1: 0x8899h |
\-------------------/ Lower Address <== stack pointer
哈哈,问题出现了,再函数返回后堆栈出现了不平衡的情况(Stack Unwinding)。
怎么办呢?好办啊,直接 pop cx pop cx 把堆栈平衡过来就好了。幸好我们只有
两个参数,要是有20个的话,那就要有20个pop cx。不说影响美观,程序效率也会
很低。所以VC++使用了这个办法解决问题:
00401082 call @ILT+5(fun) (0040100a)
00401087 add esp,8
看红色的语句,直接将esp的值加8,让堆栈变成
/-------------------\ Higher Address <== stack pointer
| 参数2: 0x1100h |
+-----------------+
| 参数1: 0x8899h |
\-------------------/ Lower Address
通过改变esp从根本上解决了Stack unwinding。(push,pop指令本质上不就是通过
改变esp来实现堆栈平衡的吗) 现在,明白了函数如何传递参数,如何调用,如何
返回。下一个问题就是看看函数如何传递返回值了。相信你早就注意到了
4: return a + b;
00401026 mov eax,dword ptr [ebp+8]
00401029 add eax,dword ptr [ebp+0Ch]
可见,函数正式用eax寄存器来保存返回值的。如果你想使用函数的返回值,那么
一定要在函数一返回就把eax寄存器的值读出来。至于为什么不用ebx,ecx...,这
个虽然没有规定,但是习惯上大家都是用eax的。而且windows程序中也明确指出了
,函数的返回值必须放入eax内。 OK,现在来解决什么是calling conversion这个
历史遗留问题。如果认真思考过,你一定想函数的参数为什么偏用堆栈转递呢,寄
存器不也可以传递吗?而且很快阿。参数的传递顺序不一定要是由后到前的,从前
到后传递也不会出现任何问题啊?再有为什么一定要等到函数返回了再处理堆栈平
衡的问题呢,能否在函数返回前就让堆栈平衡呢? 所有上述提议都是绝对可行的
,而他们之间不同的组合就造就了函数不同的调用方法。也就是你常看到或听到的
stdcall,pascal,fastcall,WINAPI,cdecl等等。这些不同的处理函数调用方式就叫
做calling convention。 默认情况下C语言使用的是cdecl方式,也就是上面提到
的。参数由右到左进栈,调用函数者处理堆栈平衡。如果你在我们刚才的程序中
fun函数前加入__stdcall,再来用上面的方法分析一下。
8: fun(0x8899,0x1100);
00401058 push 1100h ; <== 参数仍然是由右到左传递的
0040105D push 8899h
00401062 call fun (00401000)
;<== 这里没有了 add esp, 08h
1: int __stdcall fun(int a, int b) {
00401000 push ebp
00401001 mov ebp,esp
00401003 sub esp,40h
00401006 push ebx
00401007 push esi
00401008 push edi
00401009 lea edi,[ebp-40h]
0040100C mov ecx,10h
00401011 mov eax,0CCCCCCCCh
00401016 rep stos dword ptr [edi]
2: a = 0x4455;
00401018 mov dword ptr [ebp+8],4455h
3: b = 0x6677;
0040101F mov dword ptr [ebp+0Ch],6677h
4: return a + b;
00401026 mov eax,dword ptr [ebp+8]
00401029 add eax,dword ptr [ebp+0Ch]
5: }
0040102C pop edi
0040102D pop esi
0040102E pop ebx
0040102F mov esp,ebp
00401031 pop ebp
00401032 ret 8; <== ret 取出返回地址后,
; 给esp加上 8。看!堆栈平衡在函数内完成了。
; ret指令这个语法设计就是专门用来实现函数
; 内完成堆栈平衡的
于是得出结论,stdcall是由右到左传递参数,被调用函数恢复堆栈的calling
convention. 其他几种calling convention的修饰关键词分别是__pascal,
__fastcall, WINAPI(这个要包含windows.h才可以用)。现在,你可以用上面说的
方法自己分析一下他们各自的特点了。
积木
2004-04-08
打赏
举报
回复
这是一篇介绍C语言中的函数调用是如何用实现的文章。写给那些对C语言各种行为
的底层实现感兴趣人的入门级文章。如果你是C语言或者汇编、底层技术的老鸟或
是对这个问题不感兴趣,那么这篇文章只会耽误您的时间,您大可不必阅读他。当
然如果前辈们愿意为我指出不足,我将十分感谢您的指导,并对耽误您宝贵的时间
致歉。 好了,废话少说!要研究这个问题,让我们先打开VC++吧。最好是6.0的,
:-P。(什么你没有VC++,倒!....赶快装一个!@#$,要快!) 首先,让我们在VC++
里建立一个Win32 Console Application项目,并建立主文件fun.c。并输入以下内
容。
int fun(int a, int b) {
a = 0x4455;
b = 0x6677;
return a + b;
}
int main() {
fun(0x8899,0x1100);
return 0;
}
之后,最关键的是在项目设置里关闭优化功能。也就是把
Project->Setting->C/C++->Optimizations选为Disabled。编译器的优化在分析底
层实现时大多数情况不太受欢迎。 按键盘上的F10键,进入单步调试模式(Step
Over)。看到你的main函数左侧有个黄色的小箭头了吗?那个就是程序即将执行的
语句。按Alt + 8。打开反编译窗口,看到汇编语句了吗?是不是想这个样子
==> 00401078 push 1100h
0040107D push 8899h
00401082 call @ILT+5(fun) (0040100a)
00401087 add esp,8
看到两个PUSH指令了吗?再看看后面的数字,不正是我们要传递的参数吗。奇怪阿
?我们明明是先传递的0x8899怎么反倒先push 1100h呢?呵呵,这个现象就叫
Calling conversion。究竟是何方神圣,我在后面会详细的给你解释的。先别着急
。随后的Call指令的作用就是开始调用函数了。 接下来关掉反汇编窗口,在源代
码窗口按F11(Step Into)进入函数体。当看到那个黄色的小箭头指向函数名的时候
再调出反汇编窗口(Alt+8)。你会看到类似下面的代码:
1: int fun(int a, int b) {
00401000 push ebp
00401001 mov ebp,esp
00401003 sub esp,40h
00401006 push ebx
00401007 push esi
00401008 push edi
00401009 lea edi,[ebp-40h]
0040100C mov ecx,10h
00401011 mov eax,0CCCCCCCCh
00401016 rep stos dword ptr [edi]
2: a = 0x4455;
00401018 mov dword ptr [ebp+8],4455h
3: b = 0x6677;
0040101F mov dword ptr [ebp+0Ch],6677h
4: return a + b;
00401026 mov eax,dword ptr [ebp+8]
00401029 add eax,dword ptr [ebp+0Ch]
5: }
0040102C pop edi
0040102D pop esi
0040102E pop ebx
0040102F mov esp,ebp
00401031 pop ebp
00401032 ret
freefalcon
2004-04-08
打赏
举报
回复
学编译原理了吗?
学了之后就理解了
fireinsky
2004-04-08
打赏
举报
回复
建议看看c++primer
栈演进图.vsdx
示例中的5和6就通过main
函数
栈传递,而且func1在使用这2个参数时也是从main
函数
栈中取出,并没有复制一份到自己的栈中。 ④ 通过r0 ~ r3传递的参数会被压入被调
函数
栈 这也就是AAPCS中被调
函数
无需恢复r0 ~ r3的...
老生常谈C++中实参形参的传递问题
还挺复杂的~~~~~~~~⊙﹏⊙b汗,这里讲述了4种
参数传递
的情况和注意事项: 1.非引用形参 这是最普通,也是最简单的形参传递了。
参数传递
,即是使用实参副本(注意啊,是副本,不是实参本身)来初始化形参; 因此,在...
c++ primer第五版第6章答案
这段代码通过传递指针参数来交换两个整数变量的值,体现了指针在
函数
参数传递
中的应用。 以上就是基于《C++ Primer第五版》第六章答案提炼出的关键知识点及其详细解释。通过这些知识点的学习,可以帮助读者更好地...
Call 和 Return 使用
在分析"PT.ASM" 文件时,你需要关注
函数
声明、
参数传递
、调用过程和返回行为的实现细节,这将有助于你提升对汇编语言的理解。同时,"FILE-ID.DIZ" 文件提供了额外的文档支持,确保了你能够正确地解读和使用这些汇编...
python中
函数
参数传递
的三种方式_python中
函数
参数传递
的几种方法
但这条规则只回答了
函数
参数传递
的“战略问题”,并没有回答“战术问题”,也就说没有回答怎么赋值的问题。
函数
参数的使用可以分为两个方面,一是
函数
参数如何定义,二是
函数
在调用时的参数如何解析的。而后者又是由...
C语言
70,040
社区成员
243,246
社区内容
发帖
与我相关
我的任务
C语言
C语言相关问题讨论
复制链接
扫一扫
分享
社区描述
C语言相关问题讨论
社区管理员
加入社区
获取链接或二维码
近7日
近30日
至今
加载中
查看更多榜单
社区公告
暂无公告
试试用AI创作助手写篇文章吧
+ 用AI写文章