【问题标题】:C program without header没有头文件的C程序
【发布时间】:2012-01-19 17:37:03
【问题描述】:

我用 C 编写“hello world”程序。

void main()
{ printf("Hello World"); }
// note that I haven't included any header file

程序编译时警告为

vikram@vikram-Studio-XPS-1645:~$ gcc hello.c 
hello.c: In function ‘main’:
hello.c:2:2: warning: incompatible implicit declaration of built-in function ‘printf’
vikram@vikram-Studio-XPS-1645:~$ ./a.out 
Hello Worldvikram@vikram-Studio-XPS-1645:~$

这怎么可能?操作系统如何在不包含任何头文件的情况下链接库?

【问题讨论】:

    标签: c linux compilation dynamic-linking


    【解决方案1】:

    编译器通过引用一个名为printf() 的函数来构建您的源文件, 不知道它实际采用什么参数或它的返回类型是什么。生成的程序集在程序的静态数据区中包含字符串"Hello World" 的地址的push,后跟callprintf

    将目标文件链接到可执行文件时,链接器会看到对 printf 的引用并提供 C 标准库函数 printf()巧合,您传递的参数 (const char*) 与真实 printf() 的声明兼容,因此它可以正常工作。但是,请注意,您的程序隐式声明的printf() 具有返回类型int(我认为),标准printf() 也有;但是,如果它们不同,并且您要将调用 printf() 的结果分配给一个变量,那么您将处于未定义行为的境地,并且您可能会得到一个不正确的值。

    长话短说:#include 正确的标头以获得您使用的函数的正确声明,因为这种隐式声明已被弃用,因为它容易出错。

    【讨论】:

      【解决方案2】:

      printf 函数位于隐式链接的 C 库(在您的情况下为 libc)中(实际上 gcc 有一个 printf 内置函数,但它超出了重点)。

      包含标头不会为链接器带来任何函数,它只是将它们的声明告知编译器(即“它们看起来像什么”)。

      显然,您应该始终包含标头,否则您会强制编译器对函数的外观做出假设。

      【讨论】:

        【解决方案3】:

        在 C 中,如果您使用标准库函数,您必须在声明函数的地方包含标准头文件。对于printf,您必须包含stdio.h 头文件。

        在 C89(和gcc 的默认语言 GNU C89)中,有时可以省略函数声明,因为有一个称为隐式函数声明的特性:当使用函数标识符 foo 并且函数尚未声明,实现将使用此声明:

         /* foo is a function with an unspecified number of arguments */
        extern int foo();
        

        但此声明仅适用于返回 int 且参数个数未指定但固定的函数。如果函数接受可变数量的参数(如printf),则此类程序将调用未定义的行为。

        C89/C90 是这样说的:

        (C90, 6.7.1) “如果定义的函数接受可变数量的参数,但没有以省略号结尾的参数类型列表,则行为未定义。

        所以gcc 即使在 C89 和 GNU C89 中也可以编译:编译器可以拒绝编译。

        还要注意

        void main() { ... }
        

        不是main 的有效定义(至少在托管实现这可能是您的情况)。

        如果您的主函数不接受任何参数,请使用以下有效定义:

        int main(void) { ... }
        

        【讨论】:

          【解决方案4】:

          头文件通常1只包含函数声明、符号常量和宏定义;它通常不包括函数定义。

          所有stdio.h 给你的是printf 的原型声明:

          int printf(const char * restrict format, ...); // as of C99
          

          printf实现位于您的代码链接的单独库文件中。

          您的代码“有效”有两个原因:

          1. 在 C89 及更早版本下,如果编译器看到函数调用 在该函数的声明或定义之前,它将假定 该函数返回int 并采用未指定数量的 参数;

          2. printf 的实现返回一个int,你传入 一个恰好与 printf 的实现需要第一个参数。

          要回应其他人所说的话,请使用int main(void)int main(int argc, char **argv);除非您的编译器文档明确void main() 列为合法签名,否则使用它将调用未定义的行为(这意味着从您的代码运行没有明显问题到退出时崩溃到无法完全加载的所有内容)。


          1. 我说“通常”;我遇到过一些包含代码的标题,但这些标题通常是由不知道自己在做什么的人编写的。在极少数情况下将代码放在标头中是合理的,但通常这是不好的做法。

          【讨论】:

            【解决方案5】:

            hello.c:2:2: 警告:内置函数‘printf’的隐式声明不兼容

            要处理此警告,您应该包含头文件 (stdio.h)。您不小心使用了自 1999 年以来已弃用的 C 的旧功能。

            此外,链接不会失败的事实仅仅意味着标准 C 库是默认链接的。是否包含相关标题无关紧要。

            【讨论】:

            • 我确实认为隐式声明 extern int printf(); 在 C89 中对于像 printf 这样具有可变数量参数的函数甚至无效。 gcc 可以在 c89 中编译。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-03-20
            • 2011-11-27
            • 1970-01-01
            • 2013-04-17
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多