C语言中的函数原型行为出现意外情况

3

我正在学习K&R的C语言,但在书的第4.4部分中对作用域规则感到困惑。在继续之前,让我先贴出我正在使用的源文件。

#include <stdio.h>

void first(void);

int main(void) {
    printf("In main.\n");

    first();
    second();

    return 0;
}

void first(void) {
    printf("In first.\n");
}

void second(void) {
    printf("In second.\n");
}

现在,除非我比想象中的更蠢,这本书给了我这样的想法:函数原型(与函数定义在同一文件中)存在于作用域的原因。也就是说,它们的存在允许在编译文件的顶部声明该函数,以便源文件的其余部分提前被通知一个“对象”的存在,如果我可以这么说的话。
我的问题是,在我正在使用GCC版本4.7.1的Arch Linux虚拟机上,上面的代码无法编译,并显示以下错误:“second type for conflicting”。然而,当相同的代码运行到我物理机器上时,这台机器运行Ubuntu 12.04,带有GCC版本4.6.3,它可以很好地编译。
我的问题是:这是编译器的特性吗?因为如果不是,我很惊讶它能编译通过,因为没有函数原型,如果我理解正确,主要函数不应该知道second的存在。
1个回答

5
“冲突的类型”错误信息会发生在大多数 C 编译器遇到尚未声明的函数时,会隐式地声明一个返回 int 类型的函数原型。所以在 main() 函数中遇到 second() 函数时,GCC 4.7.1 推断以下定义:
int second();

然后编译器遇到函数定义时,注意到推断的原型签名与定义的签名不匹配,并因错误而中止。

至于为什么4.6.3可以成功编译这段代码,以下是一种猜测。GCC 4.6.3可能存在某些错误(或功能,由您决定),允许函数实现与其原型通过返回类型不同。由于C允许具有非void返回类型的函数在没有返回任何内容的情况下终止(这使它们的返回值未定义),所以这只是两个亚优化的事物抵消彼此的情况。

(4.6.3中的行为也可能是版本之间默认编译器选项的更改,或者您使用不同选项调用编译器造成的结果。)


不错的回答,兄弟。但我不明白的是,为什么同一个编译器在两个不同(但仍然相当新的)版本中会有如此多的不同行为。 - NlightNFotis
1
由于我的答案的第一部分——使用未声明的函数意味着根据C语言的原型推断,这意味着未声明的函数会自动带入一个即时推断的原型。始终使用-Wall编译,因为在两种情况下都会得到编译器警告。建议使用-Werror进行编译(至少对于新代码),因为它将所有警告转换为错误,确保您注意到它们。(C的这种原型推断缺陷在C99中被删除,但是GCC仍然支持它,即使在C99模式下也是如此。) - cdhowie
请注意,对于隐式声明的函数,编译器会推断为int func()而不是int func(void)。后者将不允许使用任意数量的参数调用隐式声明的函数,而实际上编译器允许使用任意数量的参数进行调用。 - Blagovest Buyukliev
@NlightNFotis 你是如何调用编译器的(使用了哪些选项)? - Daniel Fischer
@NlightNFotis 看起来 4.7 版本默认更加严格(好消息)。clang 也会产生一个错误 conflicting types for 'second',但是我的 gcc-4.6 只有一个警告。 - Daniel Fischer
显示剩余9条评论

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