c语言中函数调用的原理

2023-05-16




 

一、 函数参数传递机制的基本理论 
  函数参数传递机制问题在本质上是调用函数(过程)和被调用函数(过程)在调用发生时进行通信的方法问题。基本的参数传递机制有两种:值传递和引用传递。以下讨论称调用其他函数的函数为主调函数,被调用的函数为被调函数。
  值传递(passl-by-value)过程中,被调函数的形式参数作为被调函数的局部变量处理,即在堆栈中开辟了
内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。
  引用传递(pass-by-reference)过程中,被调函数的形式参数虽然也作为局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

二、 C语言中的函数参数传递机制

  在C语言中,值传递是唯一可用的参数传递机制。但是据笔者所知,由于受指针变量作为函数参数的影响,有许多朋友还认为这种情况是引用传递。这是错误的。请看下面的代码:
int swap(int *x, int *y)
{
int temp;
temp = *x; *x = *y; *y = temp;
return temp;
}
void main()
{
int a = 1, b = 2;
int *p1 = &a;
int *p2 = &b;
swap(p1, p2)
}
  函数swap以两个指针变量作为参数,当main()调用swap时,是以值传递的方式将指针变量p1、p2的值(也就是变量a、b的地址)放在了swap在堆栈中为形式参数x、y开辟的内存单元中。这一点从以下的
汇编代码可以看出(注释是笔者加的):
22: void main()
23: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1
0040108F mov dword ptr [ebp-8],2
14: int *p1 = &a;
00401096 lea eax,[ebp-4]
00401099 mov dword ptr [ebp-0Ch],eax
15: int *p2 = &b;
0040109C lea ecx,[ebp-8]
0040109F mov dword ptr [ebp-10h],ecx
16: swap(p1, p2);
004010A2 mov edx,dword ptr [ebp-10h] ;参数p2的值进栈
004010A5 push edx
004010A6 mov eax,dword ptr [ebp-0Ch] ;参数p1的值进栈
004010A9 push eax
004010AA call @ILT+15(swap) (00401014) ;调用swap函数
004010AF add esp,8 ;清理堆栈中的参数
17: }
  阅读上述代码要注意,INTEL80x86系列的CPU对堆栈的处理是向下生成,即从高地址单元向低地址单元生成。从上面的汇编代码可知,main()在调用swap之前,先将实参的值按从右至左的顺序压栈,即先p2进栈,再p1进栈。调用结束之后,主调函数main()负责清理堆栈中的参数。Swap 将使用这些进入堆栈的变量值。下面是swap函数的汇编代码:
14: void swap(int *x, int *y)
15: {
00401030 push ebp
00401031 mov ebp,esp ;ebp指向栈顶
……
……
16: int temp;
17: temp = *x;
4: int temp;
5: temp = *x;
00401048 mov eax,dword ptr [ebp+8] ;操作已存放在堆栈中的p1,将p1置入eax
0040104B mov ecx,dword ptr [eax] ;通过寄存器间址将*p1置入ecx
0040104D mov dword ptr [ebp-4],ecx;经由ecx将*p1置入temp变量的内存单元。以下类似
6: *x = *y;
00401050 mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr [ebp+0Ch]
00401056 mov ecx,dword ptr [eax]
00401058 mov dword ptr [edx],ecx
7: *y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return temp;
00401062 mov eax,dword ptr [ebp-4]
9: }
由上述汇编代码基本上说明了C语言中值传递的原理,只不过传递的是指针的值而已。本文后面还要论述使用引用传递的swap函数。从这些汇编代码分析,这里我们可以得到以下几点:
  1. 进程的堆栈存储区是主调函数和被调函数进行通信的主要区域。
  2. C语言中参数是从右向左进栈的。
  3. 被调函数使用的堆栈区域结构为:
    局部变量(如temp)
    返回地址
    函数参数

    低地址 
    高地址
  4. 由主调函数在调用后清理堆栈。
  5. 函数的返回值一般是放在寄存器中的
  这里尚需补充说明几点:一是参数进栈的方式。对于内部类型,由于编译器知道各类型变量使用的内存大小故直接使用push指令;对于自定义的类型(如structure),采用从源地址向目的(堆栈区)地址进行字节传送的方式入栈。二是函数返回值为什么一般放在寄存器中,这主要是为了支持中断;如果放在堆栈中有可能因为中断而被覆盖。三是函数的返回值如果很大,则从堆栈向存放返回值的地址单元(由主调函数在调用前将此地址压栈提供给被调函数)进行字节传送,以达到返回的目的。对于第二和第三点,《Thinking in 
C++》一书在第10章有比较好的阐述。四是一个显而易见的结论,如果在被调函数中返回局部变量的地址是毫无意义的;因为局部变量存于堆栈中,调用结束后堆栈将被清理,这些地址就变得无效了。

三、 C++语言中的函数参数传递机制
  C++既有C的值传递又有引用传递。在值传递上与C一致,这里着重说明引用传递。如本文前面所述,引用传递就是传递变量的地址到被调函数使用的堆栈中在C++中声明引用传递要使用"&"符号,而调用时则不用。下面的代码是使用引用传递的swap2函数和main函数:
int& swap2(int& x, int& y) 
{
int temp;
temp = x;
x = y;
y = temp;
return x;
}

void main()
{
int a = 1, b = 2;
swap2(a, b);
}
  此时函数swap2将接受两个整型变量的地址,同时返回一个其中的一个。而从main函数中对swap2的调用swap2(a, b)则看不出是否使用引用传递,是否使用引用传递,是由swap2函数的定义决定的。以下是main函数的汇编代码:
11: void main()
12: {
……
……
13: int a = 1, b = 2;
00401088 mov dword ptr [ebp-4],1 ;变量a
0040108F mov dword ptr [ebp-8],2 ;变量b
14: swap2(a, b);
00401096 lea eax,[ebp-8] ;将b的偏移地址送入eax
00401099 push eax ;b的偏移地址压栈
0040109A lea ecx,[ebp-4] ;将a的偏移地址送入ecx

0040109D push ecx ;将a的偏移地址压栈
0040109E call @ILT+20(swap2) (00401019) ;调用swap函数
004010A3 add esp,8 ;清理堆栈中的参数
15: }
可以看出,main函数在调用swap2之前,按照从右至左的顺序将b和a的偏移地
址压栈,这就是在传递变量的地址。此时swap2函数的
汇编代码是:
2: int& swap2(int& x, int& y)
3: {
00401030 push ebp
00401031 mov ebp,esp
……
……
4: int temp;
5: temp = x;
00401048 mov eax,dword ptr [ebp+8]
0040104B mov ecx,dword ptr [eax]
0040104D mov dword ptr [ebp-4],ecx
6: x = y;
00401050 mov edx,dword ptr [ebp+8]
00401053 mov eax,dword ptr [ebp+0Ch]
00401056 mov ecx,dword ptr [eax]
00401058 mov dword ptr [edx],ecx
7: y = temp;
0040105A mov edx,dword ptr [ebp+0Ch]
0040105D mov eax,dword ptr [ebp-4]
00401060 mov dword ptr [edx],eax
8: return x;
00401062 mov eax,dword ptr [ebp+8] ;返回x,由于x是外部变量的偏移地
;址,故返回是合法的
9: }
  可以看出,swap2与前面的swap函数的汇编代码是一样的。这是因为前面的swap函数接受指针变量,而指针变量的值正是地址。所以,对于这里的swap2和前面的swap来讲,堆栈中的函数参数存放的都是地址,在函数中操作的方式是一致的。但是,对swap2来说这个地址是主调函数通过将实参变量的偏移地址压栈而传递进来的--这是引用传递;而对swap来说,这个地址是主调函数通过将实参变量的值压栈而传递进来的--这是值传递,只不过由于这个实参变量是指针变量所以其值是地址而已。
  这里的关键点在于,同样是地址,一个是引用传递中的变量地址,一个是值传递中的指针变量的值。我想若能明确这一点,就不至于将C语言中的以指针变量作为函数参数的值传递情况混淆为引用传递了。
  虽然x是一个局部变量,但是由于其值是主调函数中的实参变量的地址,故在swap2中返回这个地址是合法的。
  c++ 中经常使用的是常量引用,如将swap2改为:
    Swap2(const int& x; const int& y)
  这时将不能在函数中修改引用地址所指向的内容,具体来说,x和y将不能出现在"="的左边。

四、 结束语
   本文论述了在 C 和 c++ 中函数调用的参数传递机制;同时附带说明了函数返回值的一些问题。本文示例使用的是VC++6.0。

 

可见值传递是传输了要传递的变量的一个副本,所以改变这个副本不会对调用函数造成影响,但是这个被调用函数一般有一个有用的返回值,也就是你用某个东西,在使用过程中,也许改变了它,但是时候后,你又保持原样给了人家。比如给你一个打好节的丝巾,你使用时换了另一种样式,照了像,还别人的时候,又按照人家的借你的样子还给人家,而这个照片就是需要得到的东西(类似返回值)。

而引用,就是将要传递的变量的地址传到了被调用函数中,如果在被调用函数中改变,那么就会在调用函数中改变。比如你借了人家布,如果你剪裁了不同的样式,那么还人家的样子就是你剪裁后的样子。一般c++可以使用值传递和引用传递,后者更多。因为这样不用另外在堆栈中开辟空间,而值传递就需要另外的开辟空间,对内存有一定的浪费。一般c中只使用值传递。

另外关于存储数据方面,一般是将局部变量,函数返回地址,函数参数放到堆栈中,而函数返回值一般放到寄存器中,为的是方便中断,如果有零时中断就可以直接从寄存器中处理,不用再进行压栈出栈操作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c语言中函数调用的原理 的相关文章

  • appsetting 和connectionString 的区别。

    AppSettings是ASP NET1 1时期用的 在 NET Framework 2 0中 xff0c 新增了ConnectionStrings 1 lt connectionStrings gt lt connectionString
  • 配置文件的方式总结

    1 xml文件存储配置信息 xff0c 属性中可设置输出到应用程序输出路径拷贝 xff0c 程序中读写使用 xff0c 程序修改只需修改xml文件然后覆盖到输出路径中 2 AppConfig文件存储 xff0c 可设置属性输出路径 xff0
  • 计算机大端模式和小端模式 内存对齐问题(sizeof)

    目录 43 一大端模式和小端模式的起源二什么是大端和小端三数组在大端小端情况下的存储四为什么会有大小端模式之分呢五如何判断机器的字节序内存对齐问题 再讲讲pragma pack 内存对齐二 一 大端模式和小端模式的起源 关于大端小端名词的由
  • linux虚拟机关机、重启命令

    一 shutdown 1 shutdown h now xff1a 立即关机 2 shutdown h 10 53 xff1a 到10 53关机 3 shutdown h 43 10 xff1a 10分钟后自动关机 4 shutdown r
  • C#函数的重载

    函数名必须相同方能构成函数重载 函数返回值类型 可以相同 也可以不同 注意 函数的返回类型不足以区分两个重载函数 函数参数类型 必须不同 函数参数个数 可以相同 可以不同 函数参数顺序 可以相同 可以不同 csharp view plain
  • 静态方法和实例化方法的本质区别

    本文章已收录于 xff1a 这是一个经常被时时提出来的问题 xff0c 很多时候我们以为理解了 懂了 xff0c 但深究一下 xff0c 我们却发现并不懂 方法是我们每天都在写得 xff0c 很多程序员大多都使用实例化方法 xff0c 而很
  • string,StringBuffer与StringBuilder的区别

    String 字符串常量 StringBuffer 字符串变量 xff08 线程安全 xff09 StringBuilder 字符串变量 xff08 非线程安全 xff09 简要的说 xff0c String 类型和 StringBuffe
  • C# xml文件的创建,修改和添加节点 。

    最近在做一个项目 xff0c 设计到xml文件的传输 xff0c 所以就研究了一下 xff0c NET Framework完全支持XML DOM模式 xff0c 但它不支持SAX模式 NET Framework支持两种不同的分析模式 xff
  • C#自定义ConfigSections节点操作

    sectiongroup 在config文件中加入以下节点 html view plain copy print lt configSections gt lt sectionGroup name 61 34 WebSiteInfo 34
  • 配置文件configSections节点使用实例      。

    configSections为自定义节点 xff0c 增加应用程序可移植性 xff0c 用于配置文件上传路径 xff0c 再深入应用可定义工厂方法需要加载创建的类 1 配置configSections节点 html view plain c
  • C#自定义ConfigSections节 操作 。

    sectiongroup 在config文件中加入以下节点 html view plain copy print lt configSections gt lt sectionGroup name 61 34 WebSiteInfo 34
  • C# 中的回车换行符 表示

    在 C 中 xff0c 我们用字符串 34 r n 34 表示回车换行符 string str 61 34 第一行 r n第二行 34 但是我们更推荐 Environment NewLine xff08 名称空间为 System xff09
  • C# Regex类详解

    using System using System Text RegularExpressions namespace MetarCommonSupport lt summary gt 通过Framwork类库中的Regex类实现了一些特殊
  • C#String.Split (string[], StringSplitOptions) 多参数分割得到数组

    public string Split string separator StringSplitOptions options 参数 separator 类型 xff1a System String 分隔此字符串中的子字符串的字符串数组 不
  • vim选中字符复制/剪切/粘贴

    问题描述 xff1a https www cnblogs com luosongchao p 3193153 html vim 中选中指定字符 xff0c 进行复制 剪切 粘贴 问题解决 xff1a 进入vim中visual模式 xff0c
  • for循环本质

    菜鸟 xff1a 为什么在for循环里重复定义变量不会报错 xff1f 如下代码1 xff0c 因为重复定义了两个变量 a xff0c 编译器报错 void main int a int a return 如下代码2 xff0c 用for循
  • 常见功能类库及功能

    BitConverter 数据转换类 Array 数组类 ComboBox 列表文本框
  • 基础概念笔记

    1 声明和定义的区别 声明 xff1a 是解释内存是什么类型 定义 xff1a 是赋值
  • C#中DataGridView控件使用大全

    c datagridview 分类 xff1a C C xff0b xff0b C DataGridView 动态添加新行 xff1a DataGridView控件在实际应用中非常实用 xff0c 特别需要表格显示数据时 可以静态绑定数据源

随机推荐

  • DataTable转成字符串复制到txt文本的小例子

    自己写了个DataTable转成字符串的方法 复制代码代码如下 public static string DataTableToString DataTable dt string dtstring 61 34 34 for int i 6
  • C#实现字符串按多个字符采用Split方法分割得到数组

    String字符串如何按多个字符采用Split方法进行分割呢 xff1f 本文提供VS2005和VS2003的实现方法 xff0c VS2005可以用下面的方法 xff1a string agentInfo 61 userInfo Attr
  • C#实现 UDP简单广播

    csharp view plain copy print 代码 Code highlighting produced by Actipro CodeHighlighter freeware http www CodeHighlighter
  • 事件委托 EventHandler 。

    事件就是当对象或类状态发生改变时 xff0c 对象或类发出的信息或通知 发出信息的对象或类称为 34 事件源 34 对事件进行处理的方法称为 34 接收者 34 通常事件源在发出状态改变信息时 它并不知道由哪个事件接收者来处理 这就需要一种
  • XML文件转换成字符串互相转换操作

    System Xml XmlDocument doc 61 new System Xml XmlDocument 新建对象 doc Load 34 filePath 34 XML文件路径 string content 61 doc Inne
  • dataSerVer操作方法总结

    using System using System Collections Generic using System Linq using System Text using System Data using System IO usin
  • ubuntu无法ping www.baidu.com问题

    1 使用ifconfig 查看ip 发现地址正常 2 查看dns ip地址正常 还是无法通ping www baidu com 后来把静态地址配置该为动态地址配置后成功
  • ToolStrip和ToolStripButton的用法

    假设我的toolstrip里面有三个toolstripbutton分别是tsp1 tsp2 tsp3依次加载 xff0c 如何设置tsp3显示在toolstrip的第一个按钮 ToolStripItem tsm 61 toolStrip1
  • c#多维数组的建立及操作 总结

    1C 如何定义和使用多维数组 不建议使用ArrayList xff0c 当数组里的元素是值类型在操作的时候会出现大量的装箱与拆箱步骤性能会损失许多 xff0c 而是应该用什么类型的元素创建什么类型的数组 xff0c 除非你的元素有交叉或不确
  • 在TreeView查找某一节点

    在TreeView 查找某一节点 xff0c 通常有两种方法 xff0c 一种是递归的 xff0c 一种不是递归 xff0c 但都是深度优先算法 其中 xff0c 非递归方法效率高些 xff0c 而递归算法要简洁一些 第一种 xff0c 递
  • C#的数据类型总结

    C 的数据类型可以分为3 类 数值类型 引用类型 指针类型 指针类型仅在不安全代码中使用 一 值类型 值类型包括简单值类型和复合型类型 简单值类型可以再细分为整数类型 字符类型 实数类型和布尔类型 xff1b 而复合类型则是简单类型的复合
  • C#中OpenFileDialog获取文件名和文件路径的常用方法.

    System IO Path GetFullPath openFileDialog1 FileName 绝对路径 System IO Path GetExtension openFileDialog1 FileName 文件扩展名 Syst
  • 操作XML 报错:根级别上的数据无效 和 给定编码中的字符无效 解决办法

    根级别上的数据无效 解决如下 private void button1 Click object sender EventArgs e try XmlDocument doc 61 new XmlDocument string file 6
  • DataGridRow的创建

    用原始datagridview的列名赋值的时候找不到列名 xff0c 用索引就可以 xff0c 不知道是怎么回事 DataGridViewRow dr 61 new DataGridViewRow dr CreateCells this d
  • 常用方法和属性列表

    BitConvert islittle 判断大小端 Array reverse 反排列数组
  • System.Windows.Forms.Timer与System.Timers.Timer的区别

    NET Framework里面提供了三种Timer xff1a System Windows Forms Timer System Timers Timer System Threading Timer VS NET 2005默认只有一个T
  • c++中scanf和printf

    xfeff xfeff scanf函数一般格式是 xff1a scanf 格式控制 输出表列 printf函数的一般格式是 printf 格式控制 输出表列 例3 4 用scanf和printf函数进行输入和输出 include lt io
  • win10无法上网,连网显示黄色三角形探号

    1 打开网络属性 2 打开无线电源开关 3 重启电脑 4 使用网络疑难解答 5 重启DHCP
  • 计算机程序的思维逻辑 (12) - 函数调用的基本原理

    xfeff xfeff 栈 上节我们介绍了函数的基本概念 xff0c 在最后我们提到了一个系统异常java lang StackOverflowError xff0c 栈溢出错误 xff0c 要理解这个错误 xff0c 我们需要理解函数调用
  • c语言中函数调用的原理

    xfeff xfeff 一 函数参数传递机制的基本理论 函数参数传递机制问题在本质上是调用函数 xff08 过程 xff09 和被调用函数 xff08 过程 xff09 在调用发生时进行通信的方法问题 基本的参数传递机制有两种 xff1a