社区
C语言
帖子详情
函数参数传递~~~~~~~
wq06100610
2004-04-08 01:50:05
请问~~在C/C++中,函数参数的传递到底是什么原理??比如入战出战,参数放在什么区域~能详细点举个例子吗???
...全文
351
7
打赏
收藏
函数参数传递~~~~~~~
请问~~在C/C++中,函数参数的传递到底是什么原理??比如入战出战,参数放在什么区域~能详细点举个例子吗???
复制链接
扫一扫
分享
转发到动态
举报
AI
作业
写回复
配置赞助广告
用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
三种智能算法优化PID参数整定软件 传递
函数
可任意修改 适合新手小白 Matlab语言 涵盖所有传递
函数
类型 参数表呈现清晰易懂 采用了三种不同的算法优化PID参数,包括灰狼,粒子群,麻雀算法,且做
三种智能算法优化PID参数整定软件 传递
函数
可任意修改 适合新手小白 Matlab语言 涵盖所有传递
函数
类型 参数表呈现清晰易懂 采用了三种不同的算法优化PID参数,包括灰狼,粒子群,麻雀算法,且做了简单的对比,如图1所示~ 选择了常用的ITAE指标和控制量动作的加权作为目标
函数
~ 参数表采用表格的方式呈现,一目了然。 参数包括:P, I, D参数,超调量,峰值时间,上升时间,调节时间,如图3所示,可完全满足您的需求~ 注: 1.注释清晰,适合新手小白,Matlab软件一键运行出图~ 2.仅包含Matlab代码 3.模型只是提供一个衡量数据集精度的方法,因此无法保证替数据就一定得到您满意的结果
老生常谈C++中实参形参的传递问题
函数
中参数的传递 这里说的传递当然是指 实参是如何传递给形参的啦 还挺复杂的~~~~~~~~⊙﹏⊙b汗,这里讲述了4种
参数传递
的情况和注意事项: 1.非引用形参 这是最普通,也是最简单的形参传递了。
参数传递
,即是使用实参副本(注意啊,是副本,不是实参本身)来初始化形参; 因此,在
函数
体内对形参的修改不会影响实参的值。 如果形参是指针类型的,那么
函数
体内是否可以修改指针所指向的对象的值呢? 如果您产生这样的疑问,表示您很有想法~~~ 答案是~~~需要分情况讨论。 如果
函数
的形参是非const类型的指针,则
函数
可以通过指针实现赋值,修改指针所指向对象的值。 所以,如果需要保护指针指向的值,则形参
函数
参数传递
机制
函数
参数传递
机制1
函数
参数传递
机制2 值传递、址传递和引用传递的区别2.1 功能上2.2 传递效率上2.3 执行效率上2.4 类型安全上2.5 参数检查上2.6 灵活性上3
函数
传递该用指针还是引用 1
函数
参数传递
机制
函数
参数传递
机制问题本质上是调用
函数
和被调用
函数
在调用发生时进行通信的方法问题。基本的
参数传递
机制有两种:值传递和引用传递。 在值传递过程中, 被调用的
函数
(简称被调
函数
)的形式参数(简称为形参)作为被调
函数
的局部变量处理,即在堆栈中开辟了内存空间以存放由调用其他
函数
的
函数
(简称为主调函
数组作为
函数
参数及
参数传递
一、一维数组作为
函数
参数 二、多维数组作为
函数
参数于多维数组而言,只有第一维可以选择写成数组形式或指针形式。后面的维度需要声明长度。以二维数组为例: 三、
参数传递
1.整个数组作为
函数
参数 2.数组中的元素作为
函数
参数 注:1.数组名作为
函数
实参传递时,
函数
定义处的形参可以指定数组长度也可以不指定数组长度。2.数组元素作为
函数
实参传递时,数组元素类型必须与形参数据类型一致。......
C/C++和python中的
函数
参数传递
目录一、C/C++中的
函数
参数传递
二、python中
函数
的
参数传递
问题 一、C/C++中的
函数
参数传递
1.C/C++中的普通
参数传递
和指针
参数传递
本质上都是值传递,只不过当使用指针接收实参传递的时候,传过来的是地址,所以
函数
中的任何操作都会对实参产生影响。 2.另一种方式是引用传递,但是和值传递不同的是,它并没有进行拷贝操作,而是相当于给实参一个“别名”来达到操作实参的目的。使用方式如下: #include<iostream> void reset(int &a){ a = 0;
C语言
70,026
社区成员
243,262
社区内容
发帖
与我相关
我的任务
C语言
C语言相关问题讨论
复制链接
扫一扫
分享
社区描述
C语言相关问题讨论
社区管理员
加入社区
获取链接或二维码
近7日
近30日
至今
加载中
查看更多榜单
社区公告
暂无公告
试试用AI创作助手写篇文章吧
+ 用AI写文章