C 函数中未指定数量的参数 - void foo()

Posted

技术标签:

【中文标题】C 函数中未指定数量的参数 - void foo()【英文标题】:Unspecified number of parameters in C functions - void foo() 【发布时间】:2014-03-31 04:47:42 【问题描述】:

我读到 here 在 C 中 void foo() 表示 a function foo taking an unspecified number of arguments of unspecified type

谁能给我或指出一个 C 函数接受未指定数量参数的示例?这可以在 C 中应用什么?我在网上找不到任何东西。

【问题讨论】:

首先想到的是 printf.. @tesseract,它在签名中使用...,而不是像这样的空 该声明没有指定 foo() 接受多少个参数 - 该函数很可能接受特定数量的参数(通常是这种情况),但是编译器不会帮助您获取那是正确的。有了这样的声明,程序员有责任确保参数的数量(及其类型)完全正确。 Here is 一个活生生的例子。 @JasonSwartz,您唯一能做的就是使用调用约定和您正在运行的架构的知识,并直接从堆栈中获取参数。不确定你应该使用它 IRL。 【参考方案1】:

正如在许多其他答案中所提到的,一个函数采用未指定数量的未指定类型的参数只是意味着它的调用者不知道它的定义。它与必须使用... 定义然后在stdarg.h 中使用va_list 来获取参数的可变参数函数完全不同

实际上在过去很常见,并且有许多标准函数使用该功能,例如 open()/openat()/_open() 带有可选的最后一个参数

fd = open(filename, O_RDONLY);
fd = open(filename, O_CREAT | O_WRONLY,  0777);

见open() function parameters

main() 也是一个可以接收不同数量参数的函数

int main();
int main(int argc, char **argv);
int main(int argc, char **argv, char **envp);
int main(int argc, char **argv, char **envp, char **apple);

事实上,Windows x64 调用约定是 designed to support such unspecified number of arguments,因此对于大多数现代用例来说有点不幸

【讨论】:

愚蠢的反对票有什么理由吗?【参考方案2】:

C 函数调用标准允许使用零个或多个参数调用函数,并且参数的数量可能与函数接口匹配也可能不匹配。

这种工作方式是由调用者在被调用函数返回后调整堆栈,而不是被调用函数调整堆栈,这与其他标准(如 Pascal)不同,要求被调用函数正确管理堆栈调整。

因为调用者在被调用函数被调用之前知道哪些参数及其类型已经被压入堆栈而被调用函数不知道,所以在被调用函数返回后由调用者从堆栈中清除压入的参数.

随着 C 标准的更新,函数调用接口描述变得更加复杂,以便编译器能够检测和报告原始 K&R C 标准允许编译器无法检测到的接口问题。

现在的标准是在被调用函数接口规范或声明中最后一个已知和指定的参数之后使用省略号表示的三个句点或点来指定变量参数列表。

因此,对于一些标准 C 库 I/O 函数,您会看到如下内容:

 int sprintf (char *buffer, char *format, ...);

这表明函数 sprintf 要求第一个参数是指向缓冲区的 char 指针,第二个参数是指向格式字符串的 char 指针,并且可能还有其他附加参数。在这种情况下,需要为格式字符串中的打印格式说明符插入任何附加参数。如果格式字符串只是一个没有指定格式的文本字符串(例如 %d 表示整数),则不会有其他参数。

较新的 C 标准指定一组函数/宏用于可变参数列表,即 varg 函数。使用这些函数/宏,被调用函数可以逐步遍历参数列表的变量部分并处理参数。这些函数如下所示:

int jFunc (int jj, char *form, ...)

   va_list myArgs;
   int     argOne;

   va_start (myArgs, form);
   argOne = va_arg (myArgs, int);
   va_end (myArgs);

   return 0;

变量参数列表的问题是 C 没有办法传达变量参数,甚至没有多少参数。所以功能的设计者必须提供一种机制。在 C 标准库 I/O 函数的情况下,这是通过为每个参数指定格式说明符来指示格式字符串后面的参数数量的格式完成的。而且由于没有进行一致性检查,因此您最终可能会得到一个格式字符串,该字符串指定的参数多于或少于实际参数,从而导致垃圾输出或少于预期的输出。

由于现代 C 编译器对旧 C 源代码具有一定程度的向后兼容性,这意味着您可以使用一些较旧的结构,编译器将允许它,尽管希望带有警告。

新的函数接口规范旨在减少错误使用函数的机会。所以新标准建议你使用函数接口声明,这样编译器可以通过检测接口问题和函数调用中不正确的变量使用来帮助你。

但是,如果你想成为一个冒险者,你不必使用这个安全网,所以如果你愿意,你可以定义一个带有空参数列表的函数并使用它。

您还可以找到我在question about currying in C 中添加的答案,它使用可变参数列表以及确定提供多少参数的方法。

【讨论】:

【参考方案3】:

声明void foo(); 不一定与接受可变数量参数的函数相同。

它只是一个函数声明,它没有表明函数接受的参数数量(实际上,这并不完全正确,请参阅下面关于参数提升的内容)。声明不是函数原型,它提供有关参数数量和类型的信息(使用省略号表示可变长度参数)。

函数定义(提供函数体的地方)可能有固定数量的参数,甚至可能有一个原型。

当使用这样的函数声明时,由程序员调用foo() 来获取foo() 期望的参数(及其类型)正确 - 编译器没有关于参数列表的信息@ 987654324@ 需要。它还限制了函数定义可以使用的参数类型,因为在没有原型的情况下调用函数时,默认参数提升将应用于函数调用时的参数。所以一个没有原型声明的函数只能期望得到提升的参数。

有关详细信息,请参阅以下 C99 标准部分:

6.7.5.3/14“函数声明符(包括原型)

...

函数声明器中的空列表不属于 该函数的定义指定没有关于 提供参数的数量或类型。

...

6.5.2.2 函数调用

...

如果表示被调用函数的表达式有一个类型 不包括原型,整数提升在 每个参数和具有浮点类型的参数都被提升为 双倍的。这些称为默认参数提升。如果 参数的数量不等于参数的数量, 行为未定义。如果函数是用一个类型定义的 包括一个原型,并且原型以省略号结尾 (, ...) 或提升后的参数类型不是 与参数的类型兼容,行为是 不明确的。如果函数定义的类型不 包括原型,以及提升后的参数类型 与升级后的参数不兼容, 行为未定义,但以下情况除外:

一个提升类型是有符号整数类型,另一个提升类型是对应的无符号整数类型,值为 两种类型都可以表示; 这两种类型都是指向字符类型或 void 的限定或非限定版本的指针。

【讨论】:

【参考方案4】:

这实际上意味着您没有告诉编译器该函数采用什么参数,这意味着它不会保护您免于使用任意参数集调用它。但是,您需要在定义中准确说明要实现的函数实际采用了哪些参数。

例如,如果您正在生成一个头文件来描述外部代码中的外部函数,您可以使用它,但是您不知道该函数的签名实际上是什么,它仍然可以使用您的头文件调用,但如果您提供调用结果中的错误参数未定义。

【讨论】:

【参考方案5】:

这是一个老式函数声明。

此声明:

void foo();

声明foo 是一个返回void 的函数,它接受未指定但固定数量和类型的参数。这并不意味着带有任意参数的调用是有效的;这意味着编译器无法诊断带有错误数量或类型的参数的错误调用。

某处,也许在另一个翻译单元(源文件)中,必须有一个函数的定义,也许:

void foo(x, y)
long x;
double *y;

    /* ... */

这意味着任何对foo 的调用传递longdouble* 类型的两个参数都是无效的,并且具有未定义的行为。

在 1989 年 ANSI C 标准之前,这些是语言中唯一可用的函数声明和定义,编写正确函数调用的负担完全由程序员承担。 ANSI C 添加了 prototypes,即指定函数参数类型的函数声明,允许在编译时检查函数调用。 (这个特性是从早期的 C++ 中借来的。)上面的现代等价物是:

void foo(long x, double *y);

/* ... */

void foo(long x, double *y) 
    /* ... */

旧式(非原型)声明和定义仍然合法,但它们已正式过时,这意味着原则上,它们可以从该语言的未来版本中删除-- 虽然它们仍然在 2011 年标准中,但我不知道这是否真的会发生。

没有充分的理由在现代 C 代码中使用旧式函数声明和定义。 (我已经看到在一些极端情况下使用它们的论据,但我发现它们没有说服力。)

C 还支持 variadic 函数,如 printf,它可以接受任意数量的参数,但这是一个独特的特性。必须使用原型声明可变参数函数,原型包括尾随, ...。 (调用没有可见原型的可变参数函数不是非法的,但它具有未定义的行为。)函数本身使用<stdarg.h> 中定义的宏来处理其参数。与旧式函数声明一样,没有编译时检查与, ... 对应的参数(尽管某些编译器可能会检查某些调用;例如,如果printf 调用中的参数与格式不一致,gcc 会发出警告字符串)。

【讨论】:

我在这里可能是错的,但 gcc 似乎很高兴我为可变参数函数使用旧式声明,我不确定“必须使用原型声明可变参数函数”是否正确。 @Vality:这是对“必须”这个词的草率使用。例如,在没有可见原型的情况下调用 printf 具有未定义的行为;这是一个错误,但不需要编译器来诊断它。 @JasonSwartz:老式的非原型声明是一种过时的语言特性,它们不能用于编写可以接受 2 个或 3 个参数的函数.除非您需要处理使用它们的旧代码,否则请假装它们不存在并始终使用原型。 @giorgi: void someFunc(); 是一个 old-style 非原型函数声明。它没有指定参数的数量或类型。同样,void someFunc() /* ... */ 是旧式定义;它指定没有参数,但不对调用者强制执行。旧式声明和定义已过时,但仍受到语言标准的完全支持。传递正确数量和类型的参数(在这种情况下没有)的调用具有明确定义的行为。带有不正确参数的调用,例如 someFunc(42, "Oops!"),具有未定义的行为。 为了风格和安全,最好写void someFunc(void);void someFunc(void) /* ... */

以上是关于C 函数中未指定数量的参数 - void foo()的主要内容,如果未能解决你的问题,请参考以下文章

模板函数替换仅在一个参数中未使用std :: function时才起作用

从函数中的 void * 参数返回数组

C++ 或 C 中的 foo(void) 和 foo() 有区别吗?

php函数中,多个参数的情况下怎么使其中一个参数为默认值而其他的使用指定值

显式 void 指针作为函数参数

c++11中未使用的参数