C 变分函数的调用约定
初始化变量列表时,使用宏C 变分函数的调用约定,c,calling-convention,cdecl,C,Calling Convention,Cdecl,初始化变量列表时,使用宏va_start并传递list_name,然后在va列表开始之前传递最后一个固定参数,因为“最后一个固定参数与第一个变量相邻”并且以某种方式这有助于识别堆栈中的var arg长度/位置(我不知怎么说,因为我不明白怎么说) 使用cdecl调用约定(意味着从右向左推堆栈参数)是va列表开始之前的最后一个固定参数,它在识别列表长度方面如何有用?例如,如果该参数是一个整数3,并且变量参数也有一个3,被调用方如何知道变量列表没有在这里结束,因为还有另一个3(固定参数)并且应该有结尾
va_start
并传递list_name
,然后在va列表开始之前传递最后一个固定参数
,因为“最后一个固定参数与第一个变量相邻”并且以某种方式这有助于识别堆栈中的var arg长度/位置(我不知怎么说,因为我不明白怎么说)
使用cdecl
调用约定(意味着从右向左推堆栈参数)是va列表开始之前的最后一个固定参数,它在识别列表长度方面如何有用?例如,如果该参数是一个整数3
,并且变量参数也有一个3
,被调用方如何知道变量列表没有在这里结束,因为还有另一个3
(固定参数)并且应该有结尾?例如f(inta,intb,…)
->调用f(1,3,1,2,3)
)
另一种方式是guardian“风格”,在调用函数时,您可以在变量参数的末尾添加指针,例如NULL
。同样:如果将NULL
放在堆栈的第一个位置,它有什么用?空值不应该放在参数的固定部分和可变部分之间吗?(例如f(inta,intb,…)
->调用f(a,b,NULL,param1,param2)
)如果我正确理解您的疑问,您基本上要问的是:如果所有参数都被推到堆栈中,而没有附加信息,那么变量函数如何确定其变量参数的起始位置
正如您已经注意到的,参数按声明的相反顺序推送到堆栈上:这意味着被称为f(1,2,3)
的void f(int a,…)
在调用之前首先推送3
,然后推送2
,最后推送1
那么,如何找到可变参数的起点呢
你总是知道:
堆栈顶部所在的位置
在可变参数之前需要(固定)多少个参数
因此,按相反顺序推送值是知道变量参数列表从何处开始的最简单方法。您将始终找到固定数量的变量(等于必需(固定)参数的数量,然后是所有变量参数(如果有)。这样,无论传递的参数数量如何,都可以计算参数列表的开始位置,而无需在其他任何地方传递额外信息。换句话说,可变参数的开始位置与堆栈顶部的偏移量始终相同,因为它仅取决于所需参数的数量
通过一个示例可以更清楚地说明这一点。让我们假设一个函数定义为:
int f(int n, ...) {
// ...
}
然后,编译调用f(2123456)
push 456
推123
推2
呼叫f
当f
启动时,它将发现堆栈处于以下状态:
——较低的地址----
[回信地址]当变量参数被推入堆栈时,函数什么时候知道它们何时结束?是因为您在变量部分之后找到了第一个参数,并且知道列表刚刚结束吗?为什么该参数如此特殊?它不被视为被推入堆栈的正常值吗?谢谢您的回答。@CătălinaSîrbu:“为什么参数如此特殊?它不被视为被推入堆栈的正常值吗?”-va_start
接受参数的名称,而不是值。知道参数名称可以获得位置(地址)由于连续函数的参数在堆栈上传递得很近,因此通过知道函数参数的位置,可以计算下一个参数的位置(按声明顺序)参数。因此,知道最后一个命名参数的位置可以计算第一个可变参数的位置。@Cătălinaîrbu已编辑,请参见我答案的底部。注释不用于扩展讨论;此对话已结束。