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已编辑,请参见我答案的底部。注释不用于扩展讨论;此对话已结束。