可变参数函数的调用约定

问题描述

初始化可变参数列表时,使用宏va_start并传递list_name,然后传递the last fixed parameter before the va list starts,因为“最后一个固定参数与第一个变量n相邻” 不知何故,这有助于识别堆栈中的var arg长度/位置(我之所以说是因为我不明白如何)。

使用cdecl调用约定(即从righ到左推入堆栈参数)the last fixed parameter before the va list starts在识别列表长度方面有什么用?例如,如果该参数是一个整数3,而变量参数也有一个3,那么被叫方将如何知道可变参数列表不止于此,因为还有另一个3 (固定参数),应该在那里结束?例如f(int a,int b,... )-> 通话 f(1,3,1,2,3)

反之亦然,有 guardian “样式”,您可以在调用函数时在可变参数的末尾添加NULL指针。再说一遍:如果NULL首先被推入栈,那将如何有用?难道不应该在参数的固定部分和可变部分之间插入NULL吗? (例如f(int a,... )-> 通话 f(a,b,NULL,param1,param2)

解决方法

如果我正确理解了您的疑问,那么您基本上要问的是:如果所有参数都在没有附加信息的情况下被推入堆栈,可变参数函数如何计算可变参数的起始位置?

您已经注意到,参数以相反的声明顺序推入堆栈:这意味着称为void f(int a,...)的{​​{1}}先推f(1,2,3),然后推3 ,最后2,然后再致电。

那么如何找到可变参数的开头?

您总是知道:

  1. 堆栈顶部在哪里。
  2. 在可变参数之前需要(固定)多少个参数。

因此,以相反的顺序推入值是了解变量参数列表从何处开始的最简单方法。您将始终找到固定数量的变量(等于必需(固定)参数的数量,然后是所有变量参数(如果有)),这使计算参数列表的开始成为可能变量的数量,而不需要传递其他信息,换句话说,可变参数的起始位置与堆栈顶部的偏移量始终相同,因为它仅取决于所需参数的数量。


一个例子将使这一点更加清楚。假设一个函数定义为:

1

然后,编译调用int f(int n,...) { // ... } 。在cdecl下,将产生:

f(2,123,456)

push 456 push 123 push 2 call f 启动时,它将找到处于以下状态的堆栈:

f

现在,--- lower addresses ---- [ return address ] <-- esp [ 2 ] [ 123 ] [ 456 ] --- higher addresses --- 很容易知道参数列表从何处开始,知道f是最后一个“固定”(非可变)参数:它只需要计算{{1 }}。也就是说:从n中减去保存的返回地址的固定数量(4),然后为每个固定参数减去4(nb:这是假设esp - 4 - 4)。这样,您将获得第一个可变参数的位置。

这适用于任意数量的可变参数:

esp

现在想象一下相反的情况,即以相反的顺序推入参数,您最终将sizeof(int) == 4编译为:

; f(5,1,3,4,5)      --- lower addresses ----
push 5                     [ return address ] <-- esp
push 4                     [ 5              ]
push 3                     [ 1              ]
push 2                     [ 2              ]
push 1                     [ 3              ]
push 5                     [ 4              ]
call f                     [ 5              ]
                           --- higher addresses ---

然后f(2,456)编译为:

; f(2,456)     --- lower addresses ----
push 2               [ return address   ] <-- esp
push 123             [ 456              ]
push 456             [ 123              ]
call f               [ 2                ]
                     --- higher addresses ---

现在参数列表从哪里开始?无法仅根据堆栈指针(ESP)的值和所需参数的数量来区分,因为距堆栈顶部的偏移量不再相同,但随着数量的变化而变化各种各样的论点。为了弄清楚,您要么必须对基本指针(EBP,假设您的函数甚至不需要使用它就使用它)做一些数学运算,要么传递一些附加信息。


将变量参数推入堆栈时,函数何时知道它们何时结束?

这不是调用约定所建立的。程序员将必须找出一种方法来理解基于非变量参数(或其他参数)的可变参数的数量。例如,在我上面的示例中,我只是将f(5,5)作为第一个参数传递,; f(5,5) --- lower addresses ---- push 5 [ return address ] <-- esp push 1 [ 5 ] push 2 [ 4 ] push 3 [ 3 ] push 4 [ 2 ] push 5 [ 1 ] call f [ 5 ] --- higher addresses --- 函数家族从字符串中格式标识符的数量(例如n,{{ 1}}),printf函数根据系统调用号(第一个参数)将其计算出来,依此类推...

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...