C语言中的头文件

8

我已经阅读了一段时间有关 C 语言的资料,决定写一个简单的附加程序。我的理解是,C 的头文件是“接口”(就像 Java 和其他语言一样),但你也可以定义变量,它们既可以具有固定值,也可以没有值。

于是我写了这个:

#include <stdio.h>
#include <stdlib.h>
#include "sample.h"

int main(int argc, char** argv) {
    printf("hello World\n");
    add(12, 18);
    return (EXIT_SUCCESS);
}

int add(int a, int b){
    int value = a+b;
    printf("value = %d\n", value);
    return 0;
}

它有一个看起来像这样的头文件:

#ifndef SAMPLE_H_GUARD
#define SAMPLE_H_GUARD
int add(int a, int b);
#endif

我认为头文件的定义在于定义add函数的使用方式,所以我只需要调用add函数即可 - 根据我的理解,我需要定义add函数的规则,然后实现add函数的功能...但是我对头文件的定义仍感到困惑。
此外,我阅读的许多资料都显示一个头文件用于多个C文件,而今天的许多项目每个C文件都有一个头文件,也就是说Sample.h属于Sample.c,没有别的文件。
能否有人解释一下这个问题?
我可以这样做吗:
main.c:
#include <stdio.h>
#include <stdlib.h>
#include "sample.h"

int main(int argc, char** argv) {
    printf("hello World\n");
    add(12, 18);
    return (EXIT_SUCCESS);
}

add.c

#include <stdio.h>
#include <stdlib.h>
#include "sample.h"
int add(int a, int b){
    int value = a+b;
    printf("value = %d\n", value);
    return 0;
}

sample.h

#ifndef SAMPLE_H_GUARD
#define SAMPLE_H_GUARD
int add(int a, int b);
#endif

我相信我正在阅读的书籍:C编程语言中有一个计算器示例被分成了这样,我的问题是C如何知道add在哪里定义?它根据头文件的规则进行操作,但不知道实际实现在哪里……

他们将文件拆分的示例没有像#include "add.c"这样的东西,他们只是在实现或使用此功能的文件中包含头文件。

注意:显然计算器示例和我的示例会有所不同,但基本上是相同的 - 对于那些有这本书的人来说。我只是迷失在如何有效和高效地使用头文件上。


连接器负责定位定义。 - krsteeve
作为程序员,您需要告诉链接器链接哪些目标文件和库,在目标文件和库之间,您需要确保从目标文件引用的每个函数或变量也在其他地方定义(另一个目标文件或库)。除非您有多个源文件,否则像 sample.h 这样的头文件并不是真正必要的 - 在第一个示例中,您可以将 add() 改为在 main.c 顶部声明并在底部实现的静态函数。 - Jonathan Leffler
2个回答

5
在C语言中,头文件会声明函数add以供需要的模块使用,但不会定义该函数。该函数仍需在其自己的模块中进行定义(例如,在您的情况下,即在add.c中)。
因此,通常要使函数foo对多个模块可用,您需要:
1.选择一个头文件(如果有其他相关定义等,则可能是它自己),来声明foo。例如,可能会有foo.h,其中包含void foo(...)。
2.在某个模块中,例如foo.c,您将定义完整的函数foo。
3.在任何想要调用foo的模块中,您都需要#include "foo.h"(或任何您使用的头文件),并调用该函数。
4.在编译/链接代码时,您需要确保所有模块都存在,包括已在其中定义了foo的模块foo.o或其他模块。
头文件中的声明提供了函数名、函数返回类型以及列出所有参数及其类型。这是编译器了解如何从调用模块调用函数所需的全部信息。在链接时,地址都会得到解析,以便模块确切地知道函数在其特定模块中的位置。

它只是知道吗?如果我运行这个程序,就像 - 这个头文件在哪里被调用了,让我们去那里找定义...? - LogicLooking
@LogicLooking 我不确定你所说的“它只是知道”是什么意思。你必须将正确的代码块放在正确的位置,就像我所指示的那样。链接过程确保符号对所有模块都可以正确访问。头文件声明为调用模块提供了调用函数所需的一切信息(返回和参数类型)。 - lurker
作为一个新手学习C语言,这让我更加明白了。我曾经非常困惑头文件如何能够在多个文件中使用,而且一个文件(例如main.c)可以调用在头文件中定义的各种实现。 - LogicLooking
1
@LogicLooking 关于 main.c 文件唯一特殊的地方就是它包含了 C 语言的入口函数 main。除此之外,其他规则都是一样的。实际上,它甚至不一定要叫做 main.c。你只需要在某个地方定义一个符合链接器要求的 main 函数即可。 - lurker
1
当您编译 .c 文件时,任何声明并使用但未定义的符号(函数名、外部变量)都会在目标文件中被标记为缺失。当您将所有目标文件链接在一起创建可执行文件时,链接器会注意到这些未定义的符号,并搜索正在链接的每个其他目标文件以找到定义的实际位置。如果找不到,则会从链接器获得符号解析错误。(如果您在同一命令中编译和链接源代码到可执行文件,则该过程仍然相同。) - tab
显示剩余2条评论

2
我对C头文件的理解是它们是“接口”(如Java和其他语言),但您也可以定义变量,这些变量可以有设置的值或没有设置的值。
这并不正确。您不能“定义”变量 - 您可以这样做,但如果您多次包含头文件,则在编译代码时会导致多个定义错误。
关于您的代码 - 两种变体都是正确的。 C语言使用头文件读取声明,因此头文件也是可选的。您可以将代码拆分为尽可能多的.h和.c文件。编译器将为每个.c文件创建一个对象文件。在预处理阶段,所有包含在c文件中的.h文件基本上被嵌入该C文件中。然后链接器进入图片,将对象组合以生成可执行文件。
如果我的回答有什么不清楚的地方,请不要犹豫。

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