【问题标题】:Calling method without typeless argument没有无类型参数的调用方法
【发布时间】:2020-12-03 02:43:41
【问题描述】:

我注意到 C 中有一些我不太理解的行为。如果我使用未指定类型的参数定义函数并在没有任何参数的情况下调用它,则代码会编译但会发出警告,指出参数的类型默认为 int

void f(i) {
    printf("%d", i);
}
int main(void) {
    f();
}

如果我 run this on godbolt,我会收到预期的编译器警告,并且执行会将 1 打印到 stdout

但是,如果我定义了相同的方法,但将参数显式声明为 int,编译器会因参数太少而调用的函数引发错误。

void f(int i) {
    printf("%d", i);
}
int main(void) {
    f();
}

Compiling this on godbolt 产生error: too few arguments to function 'f'

这种行为在所有现代版本的 gcc 和 clang 中都是一致的。为什么指定参数的类型会使这成为编译器错误?另外,是否定义了第一个示例的行为? gcc 和 clang 总是使 i 1 的事实让我认为这是故意的。如果有,为什么?

【问题讨论】:

  • 我敢打赌这与古代 K&R C 有关(看起来像 void f(i) int i { ... }

标签: c compilation


【解决方案1】:

第二段代码比较简单。该函数被定义为接受一个参数,main 中的函数调用没有传递任何参数。这是一个明显的错误。

第一段代码更有趣。这可以追溯到旧式函数定义,其中参数列表仅包含参数的名称,并且类型出现在结束参数列表的右括号和函数体开始之前的左大括号之间。这样一个正确定义的函数应该是这样的:

void f(i) 
  int i;
{
    printf("%d", i);
}

这种指定函数参数的风格可以追溯到最初的 K&R C,但现在被认为已弃用。 C 的这种变体还具有可以省略变量类型的属性,其中强制转换默认为int

但是,即使您确实如上例中那样在参数列表之后指定了类型,仍然不是错误。那么这是为什么呢?

关键部分来自C standard关于函数调用的第6.5.2.2节:

2 如果表示被调用函数的表达式具有包含原型的类型,则参数的数量应与参数的数量一致。每个参数应具有一种类型,使得它的值可以分配给具有其对应参数类型的非限定版本的对象。

...

8 没有隐式执行其他转换;特别是,参数的数量和类型不会与不包含函数原型声明符的函数定义中的参数的数量和类型进行比较

关于函数定义的第 6.9.1p7 节:

函数定义中的声明符指定被定义函数的名称及其参数的标识符。 如果声明符包含参数类型列表,该列表还指定了所有参数的类型;这样的声明器还可以作为函数原型,以便以后在同一翻译单元中调用同一函数。 如果声明器包含标识符列表,则应在以下声明列表中声明参数的类型。在任何一种情况下,每个参数的类型都按照 6.7.6.3 中对参数类型列表的描述进行调整;结果类型应该是一个完整的对象类型

在 K&R 样式函数定义的情况下,参数被指定为 标识符列表,而不是 参数类型列表。这意味着函数定义没有还指定函数原型,这意味着调用函数时不会检查参数的数量和类型。

【讨论】:

    【解决方案2】:

    两种类型的函数原型:

    1. 原版,被称为“K&R”[来自 Kernighan & Ritchie,C 的创造者]。
    2. 现代,称为“ANSI”[来自 ANSI 标准化委员会]。

    起初,我们只有 K&R:

    #include <stdio.h>
    
    void
    f(i)
    int i;
    {
    
        printf("%d\n",i);
    }
    
    int
    main(void)
    {
    
        f();
    
        return 0;
    }
    

    当 C 语言标准化后,又增加了另一种形式的函数原型:

    #include <stdio.h>
    
    void
    f(int i)
    {
    
        printf("%d\n",i);
    }
    
    int
    main(void)
    {
    
        f();
    
        return 0;
    }
    

    这些是函数的定义。但是,如果我们将f 放在一个单独的文件中,我们会留下 prototype 声明:

    这里是 K&R:

    #include <stdio.h>
    
    void f();
    
    int
    main(void)
    {
    
        f();
    
        return 0;
    }
    

    这里是 ANSI:

    #include <stdio.h>
    
    void f(int i);
    
    int
    main(void)
    {
    
        f();
    
        return 0;
    }
    

    如果您仔细查看 K&R 原型,则无法辨别 i 的类型。

    ANSI 纠正了这个缺陷。如果您查看该原型,i 的类型被明确指定为int


    所以,为了回答您的问题,在您的示例中,int 的语法没有是 K&R 风格的原型/定义。当我们添加int 时,它是一个ANSI 原型/定义。

    自 1990 年代 (IIRC) 的某个时候以来,K&R 就没有在新代码中广泛使用。但是,编译器理解它是为了向后兼容在 ANSI 之前编写的代码。

    旁注: 我在 1981 年开始使用 C 编程,所以我使用 K&R 至少十年(这篇文章的很多内容来自 [fading] memory)。在获得支持 ANSI 的编译器后,在新代码上试用 ANSI 原型几个月后,我将所有旧代码都转换为使用 ANSI。


    更多信息...

    见:Function declaration: K&R vs ANSI

    还有,Alternative (K&R) C syntax for function declaration versus prototypes

    【讨论】:

    • 这个问题询问编译器对参数数量的抱怨,而这个答案根本没有说明这一点。
    • 有趣!我以前从未真正遇到过任何 K&R 样式定义。我将向 dbush 提供公认的答案,只是因为他们提到了 K&R 风格缺乏原型,但 +1 是一个非常有趣的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-16
    • 2016-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多