如何解决可变参数函数的调用约定
初始化可变参数列表时,使用宏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
然后,编译调用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
函数根据系统调用号(第一个参数)将其计算出来,依此类推...
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。