-
C语言函数调用三种方式:传值调用,引用调用和传地址调用
2017-03-18 16:47:36C语言函数调用三种方式:传值调用,引用调用和传地址调用我想,你只要看了C语言上关于传值函数调用的测试题,一切都会了然于胸:
考题一:程序代码如下:
void Exchg1(int x, int y)
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=%d/n”,x,y)
}
void main()
{
int a=4,b=6;
Exchg1 (a,b) ;
printf(“a=%d,b=%d/n”,a,b)
}
输出的结果:
x=_, y=_
a=_, b=_
问下划线的部分应是什么,请完成。考题二:代码如下。
Exchg2(int *px, int *py)
{
int tmp=*px;
*px=*py;
*py=tmp;
print(“*px=%d,*py=%d/n”,*px,*py);
}
main()
{
int a=4;
int b=6;
Exchg2(&a,&b);
Print(“a=%d,b=%d/n”, a, b);
}
输出的结果为:
*px=_, *py=_
a=_, b=_
问下划线的部分应是什么,请完成。考题三:
Exchg2(int &x, int &y)
{
int tmp=x;
x=y;
y=tmp;
print(“x=%d,y=%d/n”,x,y);
}
main()
{
int a=4;
int b=6;
Exchg2(a,b);
Print(“a=%d,b=%d/n”, a, b);
}
二. 函数参数传递方式之一:值传递
1. 值传递的一个错误认识
先看题一中Exchg1函数的定义:
void Exchg1(int x, int y) //定义中的x,y变量被称为Exchg1函数的形式参数
{
int tmp;
tmp=x;
x=y;
y=tmp;
printf(“x=%d,y=%d/n”,x,y)
}
问:你认为这个函数是在做什么呀?
答:好像是对参数x,y的值对调吧?
请往下看,我想利用这个函数来完成对a,b两个变量值的对调,程序如下:
void main()
{
int a=4,b=6;
Exchg1 (a,b) //a,b变量为Exchg1函数的实际参数。
/ printf(“a=%d,b=%d/n”,a,b)
}
我问:Exchg1 ()里头的 printf(“x=%d,y=%d/n”,x,y)语句会输出什么啊?
我再问:Exchg1 ()后的 printf(“a=%d,b=%d/n”,a,b)语句输出的是什么?
程序输出的结果是:
x=6 , y=4
a=4 , b=6 //为什么不是a=6,b=4呢?奇怪,明明我把a,b分别代入了x,y中,并在函数里完成了两个变量值的交换,为什么a,b变量值还是没有交换(仍然是a==4,b==6,而不是a==6,b==4)?如果你也会有这个疑问,那是因为你跟本就不知实参a,b与形参x,y的关系了。
2. 一个预备的常识
为了说明这个问题,我先给出一个代码:
int a=4;
int x;
x=a;
x=x+3;
看好了没,现在我问你:最终a值是多少,x值是多少?
(怎么搞的,给我这个小儿科的问题。还不简单,不就是a==4 x==7嘛!)
在这个代码中,你要明白一个东西:虽然a值赋给了x,但是a变量并不是x变量哦。我们对x任何的修改,都不会改变a变量。呵呵!虽然简单,并且一看就理所当然,不过可是一个很重要的认识喔。3. 理解值传递的形式
看调用Exch1函数的代码:main()
{
int a=4,b=6;
Exchg1(a,b) //这里调用了Exchg1函数
printf(“a=%d,b=%d”,a,b)
}Exchg1(a,b)时所完成的操作代码如下所示。
int x=a;//←
int y=b;//←注意这里,头两行是调用函数时的隐含操作
int tmp;
tmp=x;
x=y;
y=tmp;
请注意在调用执行Exchg1函数的操作中我人为地加上了头两句:
int x=a;
int y=b;
这是调用函数时的两个隐含动作。它确实存在,现在我只不过把它显式地写了出来而已。问题一下就清晰起来啦。(看到这里,现在你认为函数里面交换操作的是a,b变量或者只是x,y变量呢?)
原来 ,其实函数在调用时是隐含地把实参a,b 的值分别赋值给了x,y,之后在你写的Exchg1函数体内再也没有对a,b进行任何的操作了。交换的只是x,y变量。并不是a,b。当然a,b的值没有改变啦!函数只是把a,b的值通过赋值传递给了x,y,函数里头操作的只是x,y的值并不是a,b的值。这就是所谓的参数的值传递了。
哈哈,终于明白了,正是因为它隐含了那两个的赋值操作,才让我们产生了前述的迷惑(以为a,b已经代替了x,y,对x,y的操作就是对a,b的操作了,这是一个错误的观点啊!)。三. 函数参数传递方式之二:地址传递
继续——地址传递的问题!
看题二的代码:
Exchg2(int *px, int *py)
{
int tmp=*px;
*px=*py;
*py=tmp;
print(“*px=%d,*py=%d/n”,*px,*py);
}
main()
{
int a=4;
int b=6;
Exchg2(&a,&b);
Print(“a=%d,b=%d/n”, a, b);
}
它的输出结果是:
*px=6,*py=4
a=6,b=4看函数的接口部分:Exchg2(int *px,int *py),请注意:参数px,py都是指针。 再看调用处:Exchg2(&a, &b); 它将a的地址(&a)代入到px,b的地址(&b)代入到py。同上面的值传递一样,函数调用时作了两个隐含的操作:将&a,&b的值赋值给了px,py。
px=&a;
py=&b;
呵呵!我们发现,其实它与值传递并没有什么不同,只不过这里是将a,b的地址值传递给了px,py,而不是传递的a,b的内容,而(请好好地在比较比较啦)
整个Exchg2函数调用是如下执行的:
px=&a; //
py=&b; //请注意这两行,它是调用Exchg2的隐含动作。
int tmp=*px;
*px=*py;
*py=tmp;
print(“*px=%d,*py=%d/n”,*px,*py);
这样,有了头两行的隐含赋值操作。我们现在已经可以看出,指针px,py的值已经分别是a,b变量的地址值了。接下来,对*px,*py的操作当然也就是对a,b变量本身的操作了。所以函数里头的交换就是对a,b值的交换了,这就是所谓的地址传递(传递a,b的地址给了px,py),你现在明白了吗?四. 函数参数传递方式之三:引用传递
看题三的代码:
Exchg3(int &x, int &y) //注意定义处的形式参数的格式与值传递不同
{
int tmp=x;
x=y;
y=tmp;
print(“x=%d,y=%d/n”,x,y);
}
main()
{
int a=4;
int b=6;
Exchg3(a,b); //注意:这里调用方式与值传递一样
Print(“a=%d,b=%d/n”, a, b);
}
输出结果:
x=6, y=4
a=6, b=4 //这个输出结果与值传递不同。
看到没有,与值传递相比,代码格式上只有一处是不同的,即在定义处:
Exchg3(int &x, int &y)。
但是我们发现a与b的值发生了对调。这说明了Exchg3(a,b)里头修改的是a,b变量,而不只是修改x,y了。
我们先看Exchg3函数的定义处Exchg3(int &x,int &y)。参数x,y是int的变量,调用时我们可以像值传递(如: Exchg1(a,b); )一样调用函数(如: Exchg3(a,b); )。但是x,y前都有一个取地址符号&。有了这个,调用Exchg3时函数会将a,b 分别代替了x,y了,我们称x,y分别引用了a,b变量。这样函数里头操作的其实就是实参a,b本身了,也就是说函数里是可以直接修改到a,b的值了。最后对值传递与引用传递作一个比较:
1. 在函数定义格式上有不同:
值传递在定义处是:Exchg1(int x, int y);
引用传递在这义处是:Exchg1(int &x, int &y);2. 调用时有相同的格式:
值传递:Exchg1(a,b);
引用传递:Exchg3(a,b);3. 功能上是不同的:
值传递的函数里操作的不是a,b变量本身,只是将a,b值赋给了x,y函数里操作的只是x,y变量而不是a,b,显示a,b的值不会被Exchg1函数所修改。
引用传递Exchg3(a,b)函数里是用a,b分别代替了x,y。函数里操作的是a,b。转自:http://blog.csdn.net/xiaosong2008/article/details/25430261
-
普通成员函数和内联函数调用区别(内含函数调用过程)
2017-11-23 16:36:40普通函数调用:执行到调用语句时,跳到函数代码存储区,然后执行局部变量压栈、参数压栈存、保护现场(存储函数调用后继续执行的地址)等操作。执行完后,跳回调用语句处。 如:FUN_A调用FUN_B FUN_A ebp(栈堆基址...首先要注意的是,即使在代码中声明为内联函数,编译器也不一定接受,实现的时候不一定为内联函数。
普通函数调用:执行到调用语句时,跳到函数代码存储区,然后执行局部参数压栈存、保护现场、变量压栈、(存储函数调用后继续执行的地址)等操作。执行完后,跳回调用语句处。
如:FUN_A调用FUN_B
FUN_A ebp(栈堆基址指针)入栈,esp(堆栈栈顶指针)赋值给ebp作为FUN_B基址,申请内存,压栈,FUN)B结束后,返回当前ebp恢复为FUN_A的栈顶指针esp,然后调用者A再根据ESP弹出之前的ebp,这样就恢复了调用FUN_B之前的场景。
此处参考:http://blog.csdn.net/zsy2020314/article/details/9429707
内联函数:直接把函数装入内存,在调用处直接执行函数代码。
如图所示:
补充普通函数调用过程:这个感觉比较靠谱
一直对寄存器ESP和EBP的概念总是有些混淆,查看定义ESP是栈顶指针,EBP是存取堆栈指针。还是不能很透彻理解。之后借于一段汇编代码,总算是对两者有个比较清晰的理解。
下面是按调用约定__stdcall 调用函数test(int p1,int p2)的汇编代码
;假设执行函数前堆栈指针ESP为NN
push p2 ;参数2入栈, ESP -= 4h , ESP = NN - 4h
push p1 ;参数1入栈, ESP -= 4h , ESP = NN - 8h
call test ;压入返回地址 ESP -= 4h, ESP = NN - 0Ch (EIP?)
;//进入函数内
{
push ebp ;保护先前EBP指针, EBP入栈, ESP-=4h, ESP = NN - 10h
mov ebp, esp ;设置EBP指针指向栈顶 NN-10h
mov eax, dword ptr [ebp+0ch] ;ebp+0ch为NN-4h,即参数2的位置
mov ebx, dword ptr [ebp+08h] ;ebp+08h为NN-8h,即参数1的位置
sub esp, 8 ;局部变量所占空间ESP-=8, ESP = NN-18h
...
add esp, 8 ;释放局部变量, ESP+=8, ESP = NN-10h
pop ebp ;出栈,恢复EBP, ESP+=4, ESP = NN-0Ch
ret 8 ;ret返回,弹出返回地址,ESP+=4, ESP=NN-08h, 后面加操作数8为平衡堆栈,ESP+=8,ESP=NN, 恢复进入函数前的堆栈.
}
看完汇编后,再看EBP和ESP的定义,哦,豁然开朗,
原来ESP就是一直指向栈顶的指针,而EBP只是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数参数、局部变量等。 -
理解函数调用
2018-05-17 10:00:09在函数调用过程中,需要切换堆栈, 先介绍几个常用的寄存器 ESP 堆栈指针寄存器,这个寄存器指向当前堆栈的栈顶 EBP 堆栈基地址寄存器,这个寄存器指向堆栈的栈底 EIP 指令寄存器,保存了下一条指令的地址 有了...在函数调用过程中,需要切换堆栈,
先介绍几个常用的寄存器
ESP 堆栈指针寄存器,这个寄存器指向当前堆栈的栈顶
EBP 堆栈基地址寄存器,这个寄存器指向堆栈的栈底
EIP 指令寄存器,保存了下一条指令的地址有了上面的铺垫,开始接受当函数调用时候指向CALL以及返回RET时候堆栈的状态,
当执行CALL的时候cs:eip以及指向了函数调用后的下一条指令,此时需要将cs:eip压入堆栈,以便调用完继续执行。
接下来,就是需要保存当前的堆栈,所以把当前的EBP也压入堆栈
这样原理的堆栈基地址就保存了,此时把ESP 赋值给EBP,此时
这样新的函数栈就建立了,此时的esp和ebp相等,等于说是一个空栈
那么接下来函数内部变量就可以使用这个栈了,
这样堆栈就实现了函数的调用的切换
那如果函数调用结束后呢,此时就需要还原,
退出逻辑正好相反,此时需要把EBP的内容再复制给ESP,然后再弹出(pop)EBP,这样堆栈机恢复了,然后就可以RET了,下面接直接弹出cs:eip继续执行。 -
函数调用(递归函数调用)底层机制
2013-07-01 16:32:47函数调用的底层实现 通常,当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要先完成3件事: (1)将所有的实在参数,返回地址等信息传递给被调用函数保存 (2)为被调用函数的局部...函数调用的底层实现
通常,当在一个函数的运行期间调用另一个函数时,在运行被调用函数之前,系统需要先完成3件事:
(1)将所有的实在参数,返回地址等信息传递给被调用函数保存(2)为被调用函数的局部变量分配存储区(3)将控制转移到被调用函数的入口被调用函数返回调用函数之前,系统也应该完成3件工作(1)保存被调用函数的计算结果(2)释放被调函数的数据区(3)依照被调函数保存的返回地址将控制权转移到调用函数补充:Hanoi问题描述如下:转载自:数据结构(C语言版) (55-58页) 感觉写的较详细所以就粘贴过来 -
C语言函数调用的三种方式:传值调用、引用调用和传地址调用
2018-05-03 16:07:59我想,你只要看了C语言上关于传值函数调用的测试题,一切都会了然于胸:1. 考题一:程序代码如下:void Exchg1(int x, int y) {int tmp;tmp=x;x=y;y=tmp;printf(“x=%d,y=%d/n”,x,y)}void main(){int a=4,b=6;... -
函数调用
2012-12-13 15:36:22当一个函数在运行时,如需要调用另外一个函数或者该函数本身,则需要进行以下三个操作 1.将运行函数的所有参数、值以及返回地址传递给被调函数保存 ...3.按照被调函数调用时保存的返回地址将控制转移到调用函数 -
通过函数的入口地址来调用函数
2018-06-12 09:30:27例程:int i; //定义一个测试变量void test() //定义一个函数{i = 6; //给测试变量赋初值}int main(){int addr; //定义一个保存地址的变量addr = (int)... //根据函数入口地址调用test函数//((void(*)(void))addr)(... -
Windbg如何从调用地址获得函数调用名
2013-04-26 11:27:020:000> dds poi(00405798+2) l1 0051b710 7e42974e user32!GetCursorPos 命令的原理是这样的。以下面这条指令为例: ...CALL指令调用的地址0041a908处通常是一条跳转指令: 00405798 ff2510b75100 jmp dword -
用函数的地址调用函数 C++ MFC
2016-09-12 05:04:24所以想通过函数的地址调用该函数,并且能正常的传递参数.但调用过程和被调函数是不在一个类的,调用起来又是各种麻烦 这几天我翻烂了百度, 都没找到方法,什么函数回调啊,通过函数地址调用函数啊,还有什么接口之类的 -
进程的地址空间与函数调用过程
2016-04-10 14:35:16要知道C语言的函数调用过程,首先要明白C语言中的各部分代码都出现在什么段。 首先来看一串代码,代码中的各个部分都有自己对应的段,换句话说每个段都存有C语言中的各个部分代码,而这所有的代码组合起来才成为一个... -
js中的url地址用function函数调用
2018-04-19 14:08:00url中输入调用函数,函数中调用ajax请求 转载于:https://www.cnblogs.com/nizuimeiabc1/p/8882398.html -
深入理解函数调用跳转地址
2019-02-20 22:57:20函数调用之后会返回,那返回地址如何得到? 以add函数为例接着进行分析: 在add函数设置断点,执行F11后,查看变化的寄存器有两个: EIP:保存call跳转到的指令的地址0x804841d; ESP: 该值或者是返回地址... -
函数调用过程
2018-03-22 23:20:28ax(accumulator): 可用于存放函数返回值bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址sp(stack poinger): 用于存放执行中...这里以一个简单的C语言代码为例,来分析函数调用过程 代码: 1 #include -
C语言函数调用栈(一)
2018-07-19 22:16:25编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。 不同处理器和编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一... -
将函数的地址作为参数传递给另一个函数调用
2018-12-25 14:44:45就是我们为什么要把一个函数的地址作为参数传递给另一个参数。要知道在C语言中,一个函数内部是可以直接调用其他函数的,既然可以直接调用,为什么还要用这么麻烦的办法去把函数当做参数来传递呢。下面我举个例子。 ... -
C++函数调用过程和内置函数详解
2018-03-12 10:15:01上图表示函数调用过程:①程序先执行函数调用之前的... 这样就要求在转到被调用函数之前,要记下当时执行的指令的地址,还要“保护现场”(记下当时有关的信息),方便在函数调用之后继续执行。在函数调用之后,流程 -
通过函数名调用函数和通过函数指针调用函数有什么区别呢?为什么调用函数指针没有直接调用函数效率高?
2017-11-30 23:07:05首先函数名、函数指针都表示代码段的起始地址。 1)调用函数的时候必须指定函数名,可是当有时候不确定具体调用哪个函数,当某些事件发生后才确定,所以事先就定义一个函数指针(比如回调函数) 2)函数的调用... -
函数调用原理总结
2018-11-22 15:58:00函数调用是个很有意思的东西,之前一直都很好奇:函数调用结束后是怎么知道返回什么地方?函数中的各个参数又是从哪来的呢?调用结束后又怎么将相关数据返回到调用方的?基于这些疑问,参阅很多资料,现在把自己所... -
类的函数调用
2018-06-04 10:42:042.涉及多态性时,采用虚函数和动态绑定,函数调用在运行时绑定,而非在编译时绑定,此时不再单独根据指针(引用)类型来判断调用的函数,而是根据对象中虚指针指向的虚表中的函数地址来确定调用的函数。father.h ... -
C++函数调用之传值调用、指针调用和引用调用
2019-03-07 18:40:44当调用函数时,有三种向函数传递参数的方式: 调用类型 描述 传值调用 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 指针调用 该方法把参数的地址... -
Linux函数调用与栈
2017-06-05 17:31:36栈与函数调用惯例(又称调用约定)— 基础篇 记得一年半前参加百度的校招面试时,被问到函数调用惯例的问题。当时只是懂个大概,比如常见函数调用约定类型及对应的参数入栈顺序等。最近看书过程中,重新回顾了这些... -
C语言函数调用三种方式:传值调用,引用调用和传地址调用。
2018-02-12 19:51:24同上面的值传递一样,函数调用时作了两个隐含的操作:将&a,&b的值赋值给了px,py。 px=&a; py=&b; 呵呵!我们发现,其实它与值传递并没有什么不同,只不过这里是将a,b的地址值传递给了px,py,而不是传递的a,b... -
通过函数名调用函数和通过函数指针调用函数有什么区别?
2018-10-09 14:41:36首先函数名、函数指针都表示代码段的起始地址。 1)调用函数的时候必须指定函数名,可是当有时候不确定具体调用哪个函数,当某些事件发生后才确定,所以事先就定义一个函数指针(比如回调函数) 2)函数的调用有... -
C++构造函数与析构函数调用虚函数的注意事项
2015-11-16 21:25:08在构造函数中调用虚函数,函数的入口地址是在编译时静态确定的,并未实现虚调用。但是为什么在构造函数中调用虚函数,实际上没有发生动态联编呢?1. 不要在构造函数中调用虚函数的原因第一个原因,在概念上,构造... -
关于将函数的地址作为参数传递给另一个函数调用
2015-10-26 22:38:25就是我们为什么要把一个函数的地址作为参数传递给另一个参数。要知道在C语言中,一个函数内部是可以直接调用其他函数的,既然可以直接调用,为什么还要用这么麻烦的办法去把函数当做参数来传递呢。下面我举个例子。 ...
-
VMware vSphere ESXi 7 精讲/VCSA/VSAN
-
神通科技首次公开发行股票招股说明书.pdf
-
Java IF的多选择和嵌套结构 -04天 学习笔记
-
2014年重庆理工大学《电子商务(双语)》两套期末考试试卷.pdf
-
2014年重庆理工大学《信息资源组织与管理》两套期末考试试卷.pdf
-
电池管理系统通信协议.docx
-
MySQL 数据库的基本操作(数据完整性约束)
-
2014年重庆理工大学《RFID原理与应用开发》期末考试试卷 .pdf
-
搭建ES Elasticsearch 集群
-
小记:VirtuaBox虚拟机不能启动新任务错误解决办法
-
2014年重庆理工大学《信息安全》期末考试试卷.pdf
-
12.2 布尔函数的表示
-
【链表】:输入一个链表,输出该链表中倒数第k个结点。
-
2014年重庆理工大学《数据结构》两套期末考试试卷.pdf
-
朱老师鸿蒙系列课程第1期-3.鸿蒙系统Harmonyos源码配置和管理
-
漫画算法-学习笔记(11)
-
计算多位数个十百位数并求和.txt
-
Navicat Premium.rar
-
2014年重庆理工大学《计算机专业英语》期末考试试卷.pdf
-
MYSQL长字符截断