【问题标题】:Where does a function declared in .h file find the its definition?.h 文件中声明的函数在哪里找到它的定义?
【发布时间】:2020-08-02 21:42:58
【问题描述】:

我是 C 编程新手,我对头文件包含部分有疑问。

(1) 我知道按照惯例,头文件不应包含函数定义。但是在这个简单的代码中:

#include<stdio.h>
int main(){
    printf("123");
}

printf 函数在stdio.h 中声明,但是我没有手动定义printf 函数。为什么代码编译成功?它不应该抛出类似function printf is not defined的编译错误吗?

(2)假设我有一个foo.h的头文件,它声明了函数foo

int foo(int num);

我在foo.h 的同一目录下有一个main.c 文件:

#include <stdio.h>
#include "foo.h"
int main(){
    printf("%d",foo(123));
}

我的问题是我应该在哪里定义函数foo?除了main.c里面还有别的地方吗?

如果不是,那么使用头文件有什么意义?因为在我看来,这就像:每次我们要实现在头文件中声明的函数时,都必须在.c 文件中定义它。 为什么不直接在main.c 中定义(并因此声明)函数foo

【问题讨论】:

  • Function foo() 由编译器和链接器在 make 进程告知的位置找到,具体机制因编译器和环境而异。类似地,链接器被告知或已经知道在哪里可以找到标准库函数printf()。头文件中的声明是为了让调用代码正确地接口函数。
  • foo.h中声明foo()后,我在foo.c中定义了foo(),我的IDE(或者应该说是链接器?)成功发现:foo()定义在@987654344中@ 编译主程序时。然后我将定义文件foo.c 重命名为bar.c,我的IDE 找不到定义并抛出错误。我是否应该始终将定义 .c 文件命名为与声明头文件相同?

标签: c compilation


【解决方案1】:

如果不是,那么使用头文件有什么意义?因为在我看来,这就像:每次我们要实现在头文件中声明的函数时,都必须在.c 文件中定义它。为什么不直接在main.c 中定义(并因此声明)函数foo

实际项目可能会增长到具有数百甚至数千个不同的功能,并且将它们全部保存在同一个源文件中很快变得难以为继。相反,我们将它们分组为单独的源文件,这些源文件可以独立编译成 object 文件(机器代码,但不能单独执行),然后这些目标文件可以被链接 放入可执行文件(或可链接到其他项目的库,例如标准 C 库)。

单独编译有很多好处:

  • 应用程序的不同部分可以相互独立地进行测试;
  • 可以在不同项目之间轻松共享代码;
  • 可以重新实现应用程序的某些部分,而不会影响其他部分。

您必须在调用函数之前声明它,以便编译器可以确定您正在正确地调用该函数(传递正确数量和类型的参数并适当地使用结果)。如果函数是在与调用者不同的源文件中定义的,则将声明放在单独的标头中通常是个好主意,因此无需在每个调用该函数的源文件中手动重新键入声明,只需#include包含该声明的头文件。

在头文件中定义新类型(例如 FILE 类型)和宏(例如 NULL 宏)也很常见,因此这些定义可以很容易地成为 #included 并供您的代码使用。

在您从事大型项目之前,很难看到该实用程序。

【讨论】:

    【解决方案2】:

    头文件的主要目的是声明一个模块中定义的函数和变量,以便在另一个不相关的模块中使用。

    在您的第二个示例中,您可以使用以下内容创建 foo.c:

    int foo(int num)
    {
        return num * 4;
    }
    

    然后你会编译 foo.c 和 main.c:

    gcc -c foo.c
    gcc -c main.c
    

    然后链接它们:

    gcc -o my_program foo.o main.o
    

    如果您尝试仅编译和链接 main.c,则会收到链接器错误,指出无法找到 foo 的定义。

    当您有一个大型项目时,您通常希望将函数分解为多个文件,并将相关函数放在一个 .c 文件中。

    对于在 stdio.h 中声明的函数,它们是标准 C 库的一部分。这些函数的定义会自动链接,因此您不必显式地这样做。

    【讨论】:

      【解决方案3】:

      为什么代码编译成功?

      因为您的编译器会自动链接标准 C 库。标准C库包含printf函数的定义。

      不应该抛出编译错误,比如函数 printf is not defined 吗?

      不,编译器需要提供标准符号的定义,printf 就是其中之一(在托管环境中)。

      我的问题是我应该在哪里定义函数 foo?

      任何地方你想要的。在main.c 或您想要的另一个.c 文件中。通常,一个好的做法是,我希望 foo 定义在 foo.c 中,因为它的声明在 foo.h 中。

      除了main.c里面还有别的地方吗?

      是的,在另一个翻译单元 - 在另一个 .c 文件中。例如foo.c

      如果不是,使用头文件有什么意义?

      很久很久以前,计算机还没有今天这么快——但这些计算机仍然使用 C 语言。因为历史上的编译器不可能在一个大文件中解析所有函数定义,因为例如当时可用的内存有限,为了编译速度,定义一个符号被分割成更小的单元。符号声明放在一个文件中,定义放在另一个文件中。这样,编译器会编译小的 .c 文件,这些文件只看到包含少量信息的短 .h 文件,然后在单独的阶段编译它之后,链接器将所有预编译的 .c 文件链接在一起。这样编译一次使用更少的资源。

      如今,更新的编程语言根本不在乎 - 当您使用这些语言编写 import 时,编译器就像从定义中提取声明一样工作。计算机足够快速和强大,可以轻松处理。随着 C++ 中最新添加的modules,C++ 社区希望获得类似的机制。

      为什么不在 main.c 中直接定义(并因此声明)函数 foo?

      在源文件之间划分符号定义是一个质量问题。我们程序员将信息分成更小的部分,并将它们放在单独的文件中,以便于管理大型程序。但是,当然,为什么不呢 - 如果它是一个小程序,只需在一个文件中执行,在一个 .c 文件中编写许多程序(即非常长,例如超过 3000 行)。

      【讨论】:

      • No, the compiler is required to provide definitions for standard symbols and printf is one of them (in a hosted environment). 编译器不必提供任何定义。它们必须存在于链接器使用的目标文件或库中。
      • 我想我的意思是“编译器”作为一切,整个“东西”,整个实现,任何参与将源代码翻译成生成的可执行文件的东西,包括链接器和其他任何东西.
      【解决方案4】:

      printf 函数在 stdio.h 中声明,但我没有手动 定义 printf 函数。为什么代码编译成功? 它不应该抛出类似函数 printf 的编译错误吗 没有定义?

      编译器发出调用printf 的代码。它不检查函数是否在其他编译单元或目标文件中。编译器只需知道printf 是什么——即函数的返回类型和参数是什么。稍后,linker 链接目标文件和库,并查找printf 是否存在。因为您链接到标准库 - 已编译的 printf 函数在那里并且链接成功。如果链接器在目标文件和库中找不到函数(或通常是对象),它将发出错误。

      我的问题是我应该在哪里定义函数 foo?有没有其他的 放在main.c里面吗?

      任何地方。在任何编译单元中。重要的部分是生成的包含foo 代码(定义)的目标文件必须传递给链接器。

      如果您的函数没有外部链接(即声明为static),则其定义必须与调用函数位于同一编译单元(文件)中。

      例子:

      static void foo(int n);
      
      int main(void)
      {
          foo(123);
      }
      
      static void foo(int n)
      {
          printf("%d\n", n);
      }
      

      【讨论】:

      • 稍微阐述“任何地方,任何编译单元”:一种广泛使用的约定是在foo.h 中声明一些类型和函数,并在foo.c 中实现在foo.h 中声明的那些函数。另一个约定(在某些库中使用)是在 bar.c 内实现每个函数 bar(),同时将声明分组在特定于主题的头文件中。其他约定可用。选择适合您项目的约定,然后始终如一地使用它。
      • @ndim 在foo.h 中声明foo() 后,我在foo.c 中定义了foo()。在编译主程序时,我的 IDE(或者我应该说链接器?)成功地发现:foo() 定义在foo.c 中。然后我将定义文件foo.c 重命名为bar.c,我的IDE 找不到定义并抛出错误。我应该始终将定义 .c 文件命名为与声明头文件相同吗?
      • 不,C 文件名是什么无关紧要。
      • @o_yeah IDE 尝试通过增加更多复杂性来处理构建代码的一些复杂性,以便在某些使用场景中更容易使用。如果不知道那个 IDE 是什么并且不知道那个特定的 IDE 比基本级别更重要,那么你如何告诉你的 IDE 如何链接你的程序(包括你的程序由哪些编译单元组成)是不可能知道的。
      猜你喜欢
      • 2021-01-18
      • 2020-09-28
      • 2011-11-18
      • 1970-01-01
      • 1970-01-01
      • 2010-11-29
      • 2013-01-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多