1

我目前正在更改应用程序中一类函数的函数签名。这些函数被存储在一个函数表中,所以我也期待更改这个函数表。我刚刚意识到,在某些情况下,我们已经使用了新的函数签名。但是因为所有东西都在放入函数表时被转换为正确的函数类型,所以不会发出警告。

当函数被调用时,它会被传递额外的参数,这些参数实际上并不是函数声明的一部分,但它们位于参数列表的末尾。

我无法确定这是否可以通过在 C 中传递函数参数的方式来保证。我想要做像 sprintf 这样的可变参数函数,必须是早期的参数可以正确解析,无论参数列表的末尾是什么?

它显然可以在多个平台上正常工作,但出于好奇,我想知道它是如何以及为什么工作的。

4

2 回答 2

3

但是因为所有东西都在放入函数表时被转换为正确的函数类型,所以不会发出警告。

所以编译器无济于事。C 程序员施法过多。>_<

我无法确定这是否可以通过在 C 中传递函数参数的方式来保证。我想要做像 sprintf 这样的可变参数函数,必须是可以正确解析早期参数的情况,无论参数列表的末尾是什么?

从技术上讲,您有未定义的行为。但是它是为您的平台定义的,以使用标准的 C 调用约定(参见 Scott 的回答),或者直接映射到它们的东西(通常通过将前 N 个参数映射到一组特定的处理器寄存器)。

这也经常出现在可变参数列表中。例如,printf声明如下:

int printf(const char* format, ...);

它的定义通常使用stdarg系统来处理额外的参数,它看起来像:

#include <stdarg.h>

int printf(const char* format, ...)
{
    va_list ap;
    int result;
    va_start(ap, format);
    result = vprintf(format, ap);
    va_end(ap);
    return result;
}

如果您在具有标准 C 调用约定的平台上,该va_end(ap)宏通常会变成无所事事。在这种情况下,您可以将额外的参数传递给函数。但是在某些平台上,va_end()调用需要将堆栈恢复到可预测的状态(即调用之前的位置va_start);在这些情况下,您的函数不会以它找到它的方式离开堆栈(它不会从堆栈中弹出足够多的参数),因此您的调用函数可能会在退出时崩溃,例如,当它获取一个虚假的返回值时地址。

于 2013-05-10T13:03:25.087 回答
2

您的函数肯定必须使用cdecl调用约定(http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl)。这会以相反的顺序从右到左将参数推入堆栈,确保最后一个参数可以轻松定位(堆栈顶部)并用于解释其余参数,例如printf格式字符串。清理堆栈也是调用者的责任,这比函数本身的清理要小一些(按照pascal/stdcall惯例),但确保可以使用可变参数列表,并暗示可以忽略尾随参数.

于 2013-05-10T12:54:19.177 回答