函数调用
- 函数参数传入寄存器(保护现场过程省略)或压栈
- 调用函数代码
函数中按预设的入栈顺序使用参数
调用惯例
基于函数调用过程中参数传递原理,函数调用方与被调用方对于传递和使用参数需要有一致的理解。因此需要调用惯例。
调用惯例:
- 函数参数的传递顺序和方式
- 栈的维护方式:函数调用时将所有函数参数压栈,在调用完成后需将所有参数出栈。出栈的工作可由函数的调用方完成,也可由函数本身(被调用方)完成。
- 名字修饰的策略:在原函数名基础上按设定规则加上修饰,汇编代码中可查看
C语言默认调用惯例cdecl
// 选择调用惯例// 定义__attribute__((cdecl))voidfoo(inta,intb){printf("a=%d, b=%d\n",a,b);}// 声明__attribute__((cdecl))voidfoo(int,int);x86-64 有统一的调用约定(System V AMD64 ABI), 不能自定义
变长参数函数
变长参数的标准库头文件:stdarg.h
实现不定参数的步骤:
- 定义函数:函数的最后一个参数使用省略号(…),省略号前可以有固定参数。
- 函数内创建va_list变量:这是一个在stdarg.h中定义的类型,用于访问参数列表。
- 初始化参数列表:使用va_start宏和最后一个固定参数来初始化va_list变量。
- 访问参数:使用va_arg宏和va_list变量来访问每个参数。
- 结束参数列表访问:使用va_end宏清理va_list变量。
#include<stdarg.h>#include<stdio.h>// 变长参数函数示例intfunc_name(intarg1,...){va_list ap;va_start(ap,arg1);intarg2=va_arg(ap,int);doublearg3=va_arg(ap,double);char*arg4=va_arg(ap,char*);printf("%d %lf %s\n",arg2,arg3,arg4);va_end(ap);returnarg1;}intmain(){func_name(3,1,1.5,"abc");return0;}arg1是函数的第一个参数,省略号(…)表示后续的参数数量是可变的。
要使用这种机制,需要包含stdarg.h头文件,它提供了处理可变参数的宏和函数。
可见可变参数的函数的参数分为两种类型,固定参数(只表意)和不定参数,固定参数在前不定参数在后。
变长参数宏
##为宏字符串连接
// 1.gcc编译下的定义#definemprintf(arg...)fprintf(stdout,##arg)// 2.MSVC下的定义#definemprintf(...)fprintf(stdout,__VA_ARGS__)// 使用mprintf("%d %lf %s\n",1,1.5,"abc");