使用太多参数调用C函数

时间:2013-05-10 12:49:32

标签: c casting variadic-functions

我目前正在更改应用程序中一类函数的函数签名。这些函数存储在函数表中,因此我也希望更改此函数表。我刚刚意识到在某些情况下,我们已经使用了新的函数签名。但是因为所有内容都被放入功能表中,因为它被放入函数表中,所以没有引发任何警告。

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

我无法确定这是否通过函数参数在C中传递的方式得到保证。我想要做sprintf这样的可变函数,必须是这样的情况,即前面的参数可以正确解析参数列表?

显然在多个平台上工作得很好,但出于好奇,我想知道它是如何以及为什么有效。

2 个答案:

答案 0 :(得分:2)

您的功能必须使用cdecl调用约定(http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl)。这会以相反的顺序从右到左推送堆栈上的参数,确保最后一个参数可以很容易地定位(堆栈顶部)并用于解释余数,例如printf格式字符串。调用者也负责清理堆栈,这比函数本身那样紧凑(如pascal/stdcall约定),但确保可以使用变量参数列表,并暗示尾随参数可以忽略。

答案 1 :(得分:2)

  

但是因为所有内容都被放入函数表中,因为它被放入函数表中,所以没有出现任何警告。

所以编译器没有任何帮助可言。 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之前的位置);在这些情况下,你的函数不会像它找到的那样离开堆栈(它不会从堆栈中弹回足够的参数)所以你的调用函数可能会在退出时为它返回一个伪造值地址。