函数中未指定的参数会发生什么?

11

我一直在阅读有关C语言中 function()function(void)之间的区别,并了解到:

函数声明中为空参数列表表示该函数不确定参数个数。

因此,我运行了这段代码

#include <stdio.h>

void fun();

int main(void)
{
    fun(12, 13.22, 1234567890987654321, "wow", 'c');
}

void fun()
{
    printf("What happened to those arguments?");
}

我不明白为什么C语言会允许这样的情况。所有我之前看到的相关帖子都说这是不好的实践,已经过时等等。从上面的代码中,我认为这些参数的类型也是未指定的。因此,我想知道“未指定类型的未指定参数”的原因:

  • 这些参数能做什么?

  • 在函数内部是否可以访问这些参数?


C11草案标准n1570:6.5.2.2函数调用6如果表示所调用的函数的表达式具有不包括原型的类型,[...]如果参数数量不等于参数数量,则行为未定义。 - EOF
在声明时,参数的数量(和类型)未指定。 但是,函数的使用必须与其定义的数量(和类型)相对应。`int foo(); /* 声明 / int main(void) { foo(42) / UB /; foo(3.14159); return 0; } / 定义 */ int foo(double x) { return x; }` ==> 缩进 ==> https://ideone.com/4zzlTV - pmg
2个回答

9
支持这种记法的原因是历史原因。在第一个C标准(C89/C90)之前,无法在C中使用原型;原型是标准C最重要的特性之一。因此,所有函数声明都是以“空括号”样式编写的(如果它们真的被编写了的话;大多数返回int的函数根本没有声明)。类型void也在C89/C90中添加,尽管一些编译器在标准确定之前就支持它了。
由于对于C89/C90的成功来说,现有代码的大部分仍然需要继续工作,所以标准必须允许使用空括号样式。因此,您的代码可能是以预标准C的形式编写的:
#include <stdio.h>

int fun();  /* This declaration would probably have been absent */

int main(void)
{
    fun(12, 13.22, 1234567, "wow", 'c');
    return 0;   /* This was required until C99 to give reliable exit status */
}

fun(i, d, l, s, c)      /* No return type - implicitly returns int */
long l;                 /* Defined out of sequence - bad style, but legal */
char c;                 /* Passed as int; converted to char in function */
char *s;                /* Should define all pointer arguments */
double d;               /* No definition of i; it was an int by default */
{
    printf("This is what happened to those arguments:\n");
    printf("i = %d\n", i);
    printf("d = %f\n", d);
    printf("l = %ld\n", l);
    printf("s = [%s]\n", s);
    printf("c = %c\n", c);
    /* No return statement - don't use the value from the function */
}

有兴趣的话:您可以在函数定义中省略char *s;这一行,然后仍然编译并产生相同的输出。然而,这样做是个不好的主意。您可以将int fun();这一行替换为static fun();,当没有请求诊断时,代码将编译得很干净。

即使现在使用GCC 9.3.0编译此文件(old31.c),也不会收到任何警告:

$ gcc -std=c90 -o old31 old31.c
$

您提供的示例代码违背了向后兼容性规定。使用void表示它是新代码(在许多预先标准化的C编译器中,它将是无效的,因为它使用了void)。新代码不应该没有充分理由就利用向后兼容性规定。这在1991年和当前千年都是正确的(但在1991年,有更多的充分理由来利用向后兼容性规定)。良好的预标准代码通常按照它们使用的顺序列出所有参数。省略的定义和顺序不正确的定义并不完全令人满意。
你问:
  • 这些参数能做什么?
在问题的代码中,这些参数无法被使用。调用者将值推送到堆栈上,并在函数返回时将它们弹出。调用的函数不知道它们的存在,也不能对它们进行任何操作。
  • 是否可能在函数内访问这些参数?
不可能--没有任何标准机制可以做到这一点。

8

当参数列表为空时,函数声明和函数定义之间有区别。

C标准第6.7.6.3p14节规定:

标识符列表仅声明函数参数的标识符。在函数声明符中,如果参数列表为空,则表明该函数没有参数。在不是该函数定义的声明符中,参数列表为空表示没有提供关于参数数量或类型的信息。

这意味着以下声明:

void fun();

fun函数接受未知数量的参数。而这个定义:

void fun()
{
    printf("What happened to those arguments?");
}

意思是fun不需要参数。因此,这个函数调用:
fun(12, 13.22, 1234567890987654321, "wow", 'c');

由于调用时参数的数量与实际参数的数量不匹配,因此无效并引发未定义行为。这在函数调用运算符()的第6.5.2.2p6节中有明确说明:

如果表示被调用函数的表达式的类型不包括原型,则对每个参数执行整数提升,并将类型为float的参数提升为double。这些被称为默认参数提升。如果参数数量不等于形参数量,则行为未定义。如果函数定义的类型包括原型,且原型以省略号(...)结尾或提升后的参数类型与参数类型不兼容,则行为未定义。如果函数定义的类型不包括原型,并且提升后的参数类型与提升后的参数类型不兼容,则行为未定义,但以下情况除外:一个提升类型是有符号整数类型,另一个提升类型是相应的无符号整数类型,并且该值在两种类型中都可表示;两种类型都是指向字符类型或void类型的限定或非限定版本的指针。
至于为什么允许这样做,这是一种遗留行为,可以追溯到C语言标准化之前的版本,在那里变量类型和函数返回类型默认为“int”,声明函数的方法与现在不同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接