C语言函数调用约定和返回值详情

 更新时间:2022年07月14日 15:40:41   作者:BugMaker-shen  
这篇文章主要介绍了C语言函数调用约定和返回值详情,函数调用约定不同,会影响函数生成的符号名,函数入参顺序,形参内存的清理者,更多相关需要的小伙伴可以参考下文详情介绍

 一、函数调用约定

  • _cdecl:C调用约定
  • _stdcall:Windows标准的调用约定
  • _fastcall:快速调用约定
  • _thiscall:C++的成员函数调用约定

以上的函数调用约定入参都是从右向左,只有PASCAL从左向右

函数调用约定不同,会影响函数生成的符号名,函数入参顺序,形参内存的清理者

1. 影响函数生成的符号名

在一个文件中写_cdecl的函数声明:

在另一个文件中写_stdcall的函数定义:

我们编译链接一下:

链接器找不到__cdecl sum这个函数调用的定义,将声明的地方改成__stdcall就可以链接成功

2. 影响形参内存的释放者

_stdcall

形参内存还是由调用方开辟

ret表示把栈顶元素的值(调用处下一条指令的地址)赋给PC寄存器,并出栈栈顶元素(修改esp)
ret 8表示在ret操作的基础上,执行执行指令add esp, 8

_fastcall

可以看到在_fastcall调用约定中,call指令前面并没有push操作,而是通过寄存器把实参传递到形参(没有压栈出栈,速度很快),实参在调用方栈帧上,形参在被调用方栈帧上

在_fastcall调用约定中,最多只能通过寄存器将最左边8字节的实参带给形参,多于8字节的实参还是通过push的方式带给调用方的形参

我们给sum传入三个参数,看看是谁释放形参内存 :

这就很清楚了,一共3个参数,左边的2个参数通过寄存器传递不需要清理内存,只有一个形参内存需要释放,所以显示ret 4

对于sum函数的第一个局部变量temp,在_cdecl和_stdcall中都是通过ebp-4访问的,形参是通过ebp正向偏移访问,因为形参内存在调用方的栈帧上

而在_fastcall中是通过寄存器把左边的8字节实参带给sum的形参,并存放在sum函数的栈帧上:

mov edx, dword ptr [ebp-8]
mov ecx, dword ptr [ebp-4]

所以对于sum函数的第一个局部变量temp,只能通过ebp-0Ch访问

_thiscall

对参数个数不确定的,调用者清理堆栈,否则被调用者清理堆栈

二、函数的返回值

函数的返回值分为内置类型(char、short、int、long、float、double等)、结构体类型、union、enum等

1. 0 < 返回值 <= 4字节

通过eax寄存器带出

2. 4字节 < 返回值 <= 8字节

#include <stdio.h>

typedef struct  {
	int a;
	int b;
}Data;

Data sum(Data a, Data b) {
	Data temp = { 0 };
	temp.a = a.a + b.a;
	return temp;
}
int main() {
	Data a = { 10 }; 
	Data b = {20};  
	Data ret = { 0 }; 
	ret = sum(a, b);
	return 0;
}

可以看到,4字节 < 返回值 <= 8字节时,通过eax和edx寄存器带出

3. 返回值 > 8字节

#include <stdio.h>

typedef struct  {
	int a[20];
}Data;

Data sum(Data a, Data b) {
	Data temp = { 0 };
	temp.a[0] = a.a[0] + b.a[0];
	return temp;
}

int main() {
	Data a = { 10 };
	Data b = {20};
	Data ret = { 0 };
	ret = sum(a, b);
	return 0;
}

压参数的时候,没有使用push指令,因为寄存器不够用,故使用了循环拷贝的方法,从实参的空间拷贝到形参的空间

产生临时量有三个地方:函数调用前,函数调用时return的地方,函数调用完成时。在接收大于8字节返回值时,是在函数调用前产生临时量,并把临时量内存的地址压栈,而这个临时量是用来接收返回值的

我们看到不仅仅压栈了实参a、b,还压栈了临时量的地址,可以把sum函数简单理解为如下形式:

Data sum(void* tmp_address, Data a, Data b);

我们看一下sum函数中return时的汇编指令是如何待会80字节的返回值的

最后通过eax把临时量的地址带出来,调用函数就可以通过eax拿到sum函数的返回值了

如果临时量在函数调用前产生,那被调用函数返回的时候,肯定是通过ebp+8访问临时量并写入返回值。因为ebp指向的空间保存了调用函数的栈底地址,ebp+4指向的空间保存了call指令下一条指令的地址,ebp+8指向最后一个压栈的实参,即用于带出返回值的临时量的地址

返回方式:

  • 返回值空间在[1,4],用eax寄存器
  • 返回值空间在[5,8],用eax、edx寄存器
  • 返回值空间大于8字节时,函数调用前产生临时量用于存储返回值,并把这个临时量的地址作为最后一个实参压栈,在被调用函数中通过ebp+8访问该临时量的地址

到此这篇关于C语言函数调用约定和返回值详情的文章就介绍到这了,更多相关C函数调用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++中new和delete的介绍

    C++中new和delete的介绍

    今天小编就为大家分享一篇关于C++中new和delete的介绍,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2018-12-12
  • Matlab实现生成箭头坐标轴详解

    Matlab实现生成箭头坐标轴详解

    这篇文章主要介绍了如何利用Matlab实现生成箭头坐标轴,为坐标轴增添箭头,文中的示例代码讲解详细,对我们学习Matlab有一定帮助,需要的可以参考一下
    2022-03-03
  • C语言中的结构体快排算法

    C语言中的结构体快排算法

    这篇文章主要介绍了C语言中的结构体快排算法,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • VC++实现View内容保存为图片的方法

    VC++实现View内容保存为图片的方法

    这篇文章主要介绍了VC++实现View内容保存为图片的方法,涉及VC++中Bitmap类的save方法相关使用技巧,需要的朋友可以参考下
    2016-08-08
  • 详解Visual Studio 2019(VS2019) 基本操作

    详解Visual Studio 2019(VS2019) 基本操作

    这篇文章主要介绍了详解Visual Studio 2019(VS2019) 基本操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • 详解C语言中accept()函数和shutdown()函数的使用

    详解C语言中accept()函数和shutdown()函数的使用

    这篇文章主要介绍了详解C语言中accept()函数和shutdown()函数的使用,用来操作socket相关的网络通信,需要的朋友可以参考下
    2015-09-09
  • 理解C++编程中的std::function函数封装

    理解C++编程中的std::function函数封装

    这篇文章主要介绍了理解C++编程中的std::function函数封装,std::function是C++11标准中的新特性,需要的朋友可以参考下
    2016-04-04
  • C++实现LeetCode(8.字符串转为整数)

    C++实现LeetCode(8.字符串转为整数)

    这篇文章主要介绍了C++实现LeetCode(8.字符串转为整数),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • opencv3/C++绘制几何图形实例

    opencv3/C++绘制几何图形实例

    今天小编就为大家分享一篇opencv3/C++绘制几何图形实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-12-12
  • C++中的delete不会将操作数置0

    C++中的delete不会将操作数置0

    这篇文章主要介绍了C++中的delete不会将操作数置0的相关资料,需要的朋友可以参考下
    2016-05-05

最新评论