如果我不包含头文件会发生什么

时间:2013-04-26 16:42:38

标签: c header

如果在运行c程序时不包含头文件会发生什么?我知道我收到了警告,但程序运行得很好。

我知道头文件包含函数声明。因此,当我不包含它们时,编译器如何解决它?它会检查所有头文件吗?

5 个答案:

答案 0 :(得分:7)

  

我知道我收到警告,但程序运行完美。

这是ANSI C之前的一个遗憾:该语言不需要函数原型,因此标准C允许它到今天(通常,可以生成警告以查找没有原型调用的函数)。

当你调用没有原型的函数时,C编译器会对被调用函数做出假设:

  • 假设函数的返回类型为int
  • 假设所有参数都被声明(即没有... vararg东西)
  • 假设所有参数都是默认促销后传递的任何参数,依此类推。

如果没有原型调用的函数符合这些假设,程序将正确运行;否则,这是未定义的行为。

答案 1 :(得分:4)

在1989 ANSI C标准之前,没有办法声明一个函数并指出其参数的类型。你只需要非常小心地使每个调用与被调用的函数一致,如果你弄错了(例如将int传递给sqrt()),编译器就不会发出警告。在没有可见声明的情况下,您调用的任何函数都被假定为返回int;这是“隐含的”规则。许多标准函数返回int,因此您可以通常省略#include

1989 ANSI C标准(基本上也是1990 ISO C标准)引入了原型,但没有强制要求(并且它们仍然没有)。所以,如果你打电话

int c = getchar();

它确实有效,因为getchar()会返回int

1999 ISO C标准删除了隐式int规则,并且在没有可见声明的情况下调用函数使其成为非法(实际上是约束违规)。因此,如果您在没有所需#include的情况下调用标准函数,则符合C99的编译器必须发出诊断(这可能只是一个警告)。非原型函数声明(未指定参数类型的声明)仍然合法,但它们被认为是过时的。

(2011 ISO C标准在这一特定领域没有太大变化。)

但是仍有大量的代码是为C90编译器编写的,大多数现代编译器仍然支持旧标准。<​​/ p>

因此,如果您在没有所需#include的情况下调用标准函数,可能会发生的是(a)编译器将警告您缺少的声明,以及(b)它将假设函数返回int并获取您实际传递的任何数量和类型的参数(也考虑类型提升,例如short到{{ 1}}和intfloat)。如果调用是正确的,如果编译器是宽松的,那么你的代码可能会工作 - 但是如果它失败了你可能还有一件事要担心,也许是出于某种无关的原因。 / p>

double这样的变量函数是另一回事。即使在C89 / C90中,在没有可见原型的情况下调用printf也有未定义的行为。编译器可以对可变参数函数使用完全不同的调用约定,因此printfprintf("hello")可能会生成完全不同的代码。但同样,为了与旧代码兼容,大多数编译器都使用兼容的调用约定,因此例如K&amp; R1中的第一个“hello world”程序可能仍然可以编译和运行。

您也可以为标准函数编写自己的声明;编译器不关心它是在标准头文件中还是在您自己的源文件中看到声明。但这样做没有意义。声明从标准的一个版本巧妙地改变到下一个版本,并且实现附带的标题应该是正确的。

那么如果你在没有相应的puts("hello")的情况下调用标准函数会发生什么呢?

在典型的工作环境中,这没关系,因为运气不好,您的程序将无法在代码审查中存活下来。

原则上,任何符合C99或更高版本的编译器都可能会使用致命错误消息拒绝您的程序。 (gcc将以#include的方式运行。实际上,大多数编译器只会打印警告。如果函数返回-std=c99 -pedantic-errors(或者如果忽略结果)并且所有参数类型都正确,那么调用将可能工作。如果调用不正确,编译器可能无法打印良好的诊断。如果该函数没有返回int,编译器可能会认为它确实存在,并且您将获得垃圾结果,甚至会导致程序崩溃。

因此,您可以研究我的这个答案,通过阅读C标准的各种版本进行跟进,找出您的编译器符合哪个版本的标准,并确定您可以安全地省略{{{{ 1}}标题 - 下次修改程序时可能会弄乱一些东西。

或者您可以注意编译器的警告(您已使用任何可用的命令行选项启用),阅读您调用的每个函数的文档,在顶部添加所需的int s每个源文件,而不必担心任何这些东西。

答案 2 :(得分:1)

首先:只包括它们。

如果不这样做,编译器将使用未声明函数的默认原型,即:

int functionName(int argument);

因此它将编译,并在函数可用时链接。但是你在运行时会遇到问题。

答案 3 :(得分:0)

如果遗漏标题,有很多事情你不能做:

(我希望从评论中得到更多信息,因为我的记忆失败了......)

  • 您不能使用标头中定义的任何宏。这可能很重要。
  • 编译器无法检查您是否正在正确调用函数,因为标题为其定义了参数。

答案 4 :(得分:0)

为了与旧程序兼容,C编译器可以编译代码调用尚未声明的函数,假设参数和返回值的类型为int。怎么会发生什么?例如,请参阅此问题:Troubling converting string to long long in C我认为如果您不包含必要的标题,那么您可以遇到的问题很好地说明,因此不要声明您使用的函数。该家伙发生了什么事,他试图使用atoll而不包括stdlib.h宣布atoll的地方:

char s[30] = { "115" };
long long t = atoll(s);
printf("Value is: %lld\n", t);

令人惊讶的是,这打印了0,而不是115,正如所料!为什么?因为编译器没有看到atoll的声明并且假设它的返回值是int,所以只选择函数在堆栈上留下的部分值,换句话说返回值得到了截断。

这就是为什么建议使用-Wall编译代码的原因(所有警告都已开启)。