C语言为什么允许我调用未声明的函数?

3
我有两个文件:test1.ctest2.c,它们都包含了 main() 函数。 test1.c:
#include <stdio.h>  // printf() function declaration/prototype

// function definition
void say_hello() {
  printf("\tHello, world!\n");
}

test2.c:

#include <stdio.h>  // printf() function declaration/prototype
int main() {
  printf("Inside main()\n");
  say_hello();

  return 0;
}

这是我的 Makefile 文件:
a.out: test1.o test2.o
    $(CXX) -o a.out test1.o test2.o

test1.o: test1.c
    $(CXX) -c test1.c

test2.o: test2.c
    $(CXX) -c test2.c

现在应该很清楚问题出在哪里了:test2.c中的main()函数调用say_hello()却没有声明它!我运行以下命令,使用gcc编译器:make CXX=gcc 我会在屏幕上看到这个警告:
gcc -c test1.c
gcc -c test2.c
test2.c: In function ‘main’:
test2.c:16:3: warning: implicit declaration of function ‘say_hello’ [-Wimplicit-function-declaration]
   say_hello();
   ^
gcc -o a.out test1.o test2.o

尽管 *.o 文件已经被编译并链接到可执行文件中。这很奇怪。当我运行 a.out 文件时,看到 main() 成功调用了 say_hello() 函数,而该函数并未在 main 的翻译单元内声明,就好像根本没有任何问题一样,这让我感到惊讶!我推断,由于 say_hello() 没有在 test2.c 中先声明,因此根本不应该被 main() 调用。请注意,我已经添加了对 #include 的注释。这些预处理指令包括函数的声明/原型,将它们的作用域扩展到相应的 *.c 文件中。这就是为什么我们能够使用它们。没有函数声明/原型 == 在该翻译单元中没有作用域,或者我之前一直是这么认为的,直到现在。

然后我将上述代码编译为 C++ 代码:make CXX=g++

屏幕上会显示以下错误:

test2.c: In function ‘int main()’:
test2.c:16:13: error: ‘say_hello’ was not declared in this scope
   say_hello();
             ^
makefile:18: recipe for target 'test2.o' failed
make: *** [test2.o] Error 1

"g++ 做了它该做的,并停止了编译过程。但是 gcc 没有这样做!发生了什么?这是 C 编程语言的优点吗?还是编译器的问题?"

5
选一种语言。 - Mad Physicist
6
“这是C编程语言的一个优点吗?” - 是的,确实是。C编程语言天真地认为程序员知道自己在做什么。 - Eugene Sh.
1
@Galaxy 声明更多或更少只是告诉您某个东西的类型(在C和C++中函数的类型通常称为签名)。它实际上与多个翻译单元等没有任何关系,尽管它也很有用。 - Cubic
2
@Galaxy 这个问题不能同时涉及到两种语言,因为C++不允许这样做。;-) - Konrad Rudolph
3
这是一种向后兼容性的问题。C语言最初没有原型。程序员应该知道如何调用每个函数,每个未声明的函数假定返回int(并且需要一个未指定数量的升级参数,这是没有原型的结果)。 - Petr Skocik
显示剩余8条评论
1个回答

5
很简单,因为C语言允许调用未声明的函数,而C++不允许。无论哪种情况,gcc都会发出警告,你可能需要认真对待这些警告。

但是为什么C允许调用未声明的函数,而C++不允许呢? - Galaxy
3
因为它可以。C++是一种具有更严格规则的不同语言。 - Eugene Sh.
1
或者加上-Werror选项(-Werror=implicit-function-declaration),这就更严重了。 :) - Petr Skocik
@Galaxy 历史原因,源自只有一种存储类型的B语言,这种类型在C语言中变成了int。虽然你需要在块内声明你使用的函数,比如main() { extrn /* sic */ printn, char, putchar; … },但我不确定这是否是强制性的。 - Triang3l
考虑制作程序的两个部分:编译和链接。为什么不尝试在test1.c中摆脱say_hello并看看会发生什么?程序可以编译成功,但当链接器尝试搜索say_hello(因为它在test2.c中被调用)时,它会失败。因此,表达C和C++之间差异的一种方式是,C将抛出链接器错误,而C++将抛出编译器错误。 - Max Coplan

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