函数调用
- 函数参数传入寄存器(保护现场过程省略)或压栈
- 调用函数代码
函数中按预设的入栈顺序使用参数
调用惯例
基于函数调用过程中参数传递原理,函数调用方与被调用方对于传递和使用参数需要有一致的理解。因此需要调用惯例。
调用惯例:
- 函数参数的传递顺序和方式
- 栈的维护方式:函数调用时将所有函数参数压栈,在调用完成后需将所有参数出栈。出栈的工作可由函数的调用方完成,也可由函数本身(被调用方)完成。
- 名字修饰的策略:在原函数名基础上按设定规则加上修饰,汇编代码中可查看
C语言默认调用惯例cdecl
c
// 选择调用惯例
// 定义
__attribute__((cdecl)) void foo(int a, int b)
{
printf("a=%d, b=%d\n", a, b);
}
// 声明
__attribute__((cdecl)) void foo(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变量。
c
#include <stdarg.h>
#include <stdio.h>
// 变长参数函数示例
int func_name(int arg1, ...)
{
va_list ap;
va_start(ap, arg1);
int arg2 = va_arg(ap, int);
double arg3 = va_arg(ap, double);
char *arg4 = va_arg(ap, char *);
printf("%d %lf %s\n", arg2, arg3, arg4);
va_end(ap);
return arg1;
}
int main() {
func_name(3, 1, 1.5, "abc");
return 0;
}
arg1是函数的第一个参数,省略号(...)表示后续的参数数量是可变的。
要使用这种机制,需要包含stdarg.h头文件,它提供了处理可变参数的宏和函数。
可见可变参数的函数的参数分为两种类型,固定参数(只表意)和不定参数,固定参数在前不定参数在后。
变长参数宏
##为宏字符串连接
c
// 1.gcc编译下的定义
#define mprintf(arg...) fprintf(stdout, ##arg)
// 2.MSVC下的定义
#define mprintf(...) fprintf(stdout, __VA_ARGS__)
// 使用
mprintf("%d %lf %s\n", 1, 1.5, "abc");