头文件和源文件在C中如何工作?

时间:2021-08-18 15:05:36

I've perused the possible duplicates, however none of the answers there are sinking in.

我已经仔细研究了可能的重复,但是没有一个答案正在下沉。

tl;dr: How are source and header files related in C? Do projects sort out declaration/definition dependencies implicitly at build time?

源文件和头文件在C中是如何关联的?项目是否在构建时隐式地排序声明/定义依赖项?

I'm trying to understand how the compiler understands the relationship between .c and .h files.

我试图理解编译器如何理解.c和.h文件之间的关系。

Given these files:

考虑到这些文件:

header.h:

header.h:

int returnSeven(void);

source.c:

source.c:

int returnSeven(void){
    return 7;
}

main.c:

c:

#include <stdio.h>
#include <stdlib.h>
#include "header.h"
int main(void){
    printf("%d", returnSeven());
    return 0;
}

Will this mess compile? I'm currently doing my work in NetBeans 7.0 with gcc from Cygwin which automates much of the build task. When a project is compiled will the project files involved sort out this implicit inclusion of source.c based on the declarations in header.h?

将这个烂摊子编译?我目前正在使用Cygwin的gcc在NetBeans 7.0中完成我的工作,Cygwin使大部分构建任务自动化。当一个项目被编译时,项目文件就会将这个隐式包含的源代码进行分类。c基于head。h?

5 个解决方案

#1


59  

Converting C source code files to an executable program is normally done in two steps: compiling and linking.

将C源代码文件转换为可执行程序通常分为两步:编译和链接。

First, the compiler converts the source code to object files (*.o). Then, the linker takes these object files, together with statically-linked libraries and creates an executable program.

首先,编译器将源代码转换为目标文件(*.o)。然后,链接器将这些对象文件连同静态链接库一起创建一个可执行程序。

In the first step, the compiler takes a compilation unit, which is normally a preprocessed source file (so, a source file with the contents of all the headers that it #includes) and converts that to an object file.

在第一步中,编译器需要一个编译单元,它通常是一个预处理的源文件(因此,一个源文件包含它所包含的所有header的内容)并将其转换为一个对象文件。

In each compilation unit, all the functions that are used must be declared, to let the compiler know that the function exists and what its arguments are. In your example, the declaration of the function returnSeven is in the header file header.h. When you compile main.c, you include the header with the declaration so that the compiler knows that returnSeven exists when it compiles main.c.

在每个编译单元中,必须声明所使用的所有函数,以让编译器知道函数的存在及其参数。在您的示例中,函数returnSeven的声明位于header文件header.h中。当你编译主要。c,您将标头包含在声明中,以便编译器在编译main.c时知道returnSeven存在。

When the linker does its job, it needs to find the definition of each function. Each function has to be defined exactly once in one of the object files - if there are multiple object files that contain the definition of the same function, the linker will stop with an error.

当链接器完成它的工作时,它需要找到每个函数的定义。每个函数都必须在一个对象文件中定义一次——如果有多个对象文件包含相同函数的定义,链接器将以错误停止。

Your function returnSeven is defined in source.c (and the main function is defined in main.c).

函数returnSeven是在源代码中定义的。c(主函数在main.c中定义)。

So, to summarize, you have two compilation units: source.c and main.c (with the header files that it includes). You compile these to two object files: source.o and main.o. The first one will contain the definition of returnSeven, the second one the definition of main. Then the linker will glue those two together in an executable program.

所以,总结一下,你有两个编译单元:源。c和主要。c(包含头文件)。将它们编译为两个对象文件:源文件。o和main.o。第一个是returnSeven的定义,第二个是main的定义。然后链接器将这两个连接在一个可执行程序中。

About linkage:

关于链接:

There is external linkage and internal linkage. By default, functions have external linkage, which means that the compiler makes these functions visible to the linker. If you make a function static, it has internal linkage - it is only visible inside the compilation unit in which it is defined (the linker won't know that it exists). This can be useful for functions that do something internally in a source file and that you want to hide from the rest of the program.

有外部联动和内部联动。默认情况下,函数具有外部链接,这意味着编译器使这些函数对链接器可见。如果你让一个函数是静态的,它有内部链接——它只在定义它的编译单元中可见(链接器不会知道它的存在)。这对于在源文件内部执行某些操作并希望对程序的其他部分进行隐藏的函数是有用的。

#2


25  

The C language has no concept of source files and header files (and neither does the compiler). This is merely a convention; remember that a header file is always #included into a source file; the preprocessor literally just copy-pastes the contents, before proper compilation begins.

C语言没有源文件和头文件的概念(编译器也没有)。这只是一种惯例;请记住,头文件总是包含在源文件中的#;在开始适当的编译之前,预处理程序实际上只是复制粘贴内容。

Your example should compile (foolish syntax errors notwithstanding). Using GCC, for example, you might first do:

您的示例应该编译(尽管有一些愚蠢的语法错误)。例如,使用GCC,您可以先做:

gcc -c -o source.o source.c
gcc -c -o main.o main.c

This compiles each source file separately, creating independent object files. At this stage, returnSeven() has not been resolved inside main.c; the compiler has merely marked the object file in a way that states that it must be resolved in the future. So at this stage, it's not a problem that main.c can't see a definition of returnSeven(). (Note: this is distinct from the fact that main.c must be able to see a declaration of returnSeven() in order to compile; it must know that it is indeed a function, and what its prototype is. That is why you must #include "source.h" in main.c.)

这将分别编译每个源文件,创建独立的对象文件。在此阶段,returnSeven()尚未在main.c中得到解决;编译器只是标记对象文件的方式表明它必须在将来被解析。所以在这个阶段,这不是主要的问题。c看不到returnSeven()的定义。(注:这与main的事实不同。c必须能够看到returnSeven()声明才能编译;它必须知道它确实是一个函数,以及它的原型是什么。这就是为什么您必须#include“source”。c h”)。

You then do:

你做的事:

gcc -o my_prog source.o main.o

This links the two object files together into an executable binary, and performs resolution of symbols. In our example, this is possible, because main.o requires returnSeven(), and this is exposed by source.o. In cases where everything doesn't match up, a linker error would result.

这将两个对象文件链接到一个可执行的二进制文件中,并执行符号解析。在我们的示例中,这是可能的,因为main。o需要returnSeven(),这是由source.o公开的。在一切都不匹配的情况下,会产生链接器错误。

#3


10  

There is nothing magic about compilation. Nor automatic!

编译没有什么神奇之处。还是自动!

Header files basically provide information to the compiler, almost never code.
That information alone, is usually not enough to create a full program.

头文件基本上向编译器提供信息,几乎从不编写代码。这些信息本身通常不足以创建完整的程序。

Consider the "hello world" program (with the simpler puts function):

考虑“hello world”程序(带有更简单的put函数):

#include <stdio.h>
int main(void) {
    puts("Hello, World!");
    return 0;
}

without the header, the compiler does not know how to deal with puts() (it is not a C keyword). The header lets the compiler know how to manage the arguments and return value.

没有header,编译器不知道如何处理put()(它不是C关键字)。头让编译器知道如何管理参数和返回值。

How the function works, however, is not specified anywhere in this simple code. Somebody else has written the code for puts() and included the compiled code in a library. The code in that library is included with the compiled code for your source as part of the compilation process.

然而,在这个简单的代码中,函数的工作方式并没有被指定。其他人已经为put()编写了代码,并将编译后的代码包含在库中。该库中的代码包含了作为编译过程一部分的源代码的编译代码。

Now consider you wanted your own version of puts()

现在考虑您想要自己版本的put ()

int main(void) {
    myputs("Hello, World!");
    return 0;
}

Compiling just this code gives an error because the compiler has no information about the function. You can provide that information

仅仅编译这段代码就会产生错误,因为编译器没有关于这个函数的信息。你可以提供这些信息。

int myputs(const char *line);
int main(void) {
    myputs("Hello, World!");
    return 0;
}

and the code now compiles --- but does not link, ie does not produce an executable, because there is no code for myputs(). So you write the code for myputs() in a file called "myputs.c"

现在的代码编译——但不链接,ie不生成可执行文件,因为myput()没有代码。因此,您将myput()的代码写入一个名为“myputs.c”的文件中

#include <stdio.h>
int myputs(const char *line) {
    while (*line) putchar(*line++);
    return 0;
}

and you have to remember to compile both your first source file and "myputs.c" together.

你必须记住编译你的第一个源文件和“myput”。c”在一起。

After a while your "myputs.c" file has expanded to a hand full of functions and you need to include the information about all the functions (their prototypes) in the source files that want to use them.
It is more convenient to write all the prototypes in a single file and #include that file. With the inclusion you run no risk of making a mistake when typing the prototype.

过了一会儿,你的“米普特人”。c"文件已经扩展到满手的函数,您需要在希望使用它们的源文件中包含关于所有函数(它们的原型)的信息。将所有原型写在一个文件中更方便,#include包含该文件。通过包含,您在输入原型时不会出错。

You still have to compile and link all the code files together though.

但是您仍然需要编译并链接所有的代码文件。


When they grow even more, you put all the already compiled code in a library ... and that's another story :)

当它们增长得更多时,你把所有已经编译好的代码放在一个库中……这是另一个故事

#4


4  

Header files are used to separate the interface declarations which correspond to the implementations in the source files. They're abused in other ways, but this is the common case. This isn't for the compiler, it's for the humans writing the code.

头文件用于分离与源文件中的实现相对应的接口声明。它们在其他方面被滥用,但这是常见的情况。这不是为编译器做的,而是为编写代码的人做的。

Most compilers don't actually see the two files separately, they are combined by the preprocessor.

大多数编译器实际上并没有分别看到这两个文件,它们是由预处理器组合的。

#5


2  

The compiler itself has no specific "knowledge" of relationships between source files and header files. Those types of relationships are typically defined by project files (e.g., makefile, solution, etc.).

编译器本身不知道源文件和头文件之间的关系。这些类型的关系通常由项目文件(例如makefile、解决方案等)定义。

The given example appears as if it would compile correctly. You would need to compile both source files and then the linker would need both object files to produce the executable.

给定的示例看起来似乎可以正确地编译。您需要编译两个源文件,然后链接器需要两个对象文件来生成可执行文件。

#1


59  

Converting C source code files to an executable program is normally done in two steps: compiling and linking.

将C源代码文件转换为可执行程序通常分为两步:编译和链接。

First, the compiler converts the source code to object files (*.o). Then, the linker takes these object files, together with statically-linked libraries and creates an executable program.

首先,编译器将源代码转换为目标文件(*.o)。然后,链接器将这些对象文件连同静态链接库一起创建一个可执行程序。

In the first step, the compiler takes a compilation unit, which is normally a preprocessed source file (so, a source file with the contents of all the headers that it #includes) and converts that to an object file.

在第一步中,编译器需要一个编译单元,它通常是一个预处理的源文件(因此,一个源文件包含它所包含的所有header的内容)并将其转换为一个对象文件。

In each compilation unit, all the functions that are used must be declared, to let the compiler know that the function exists and what its arguments are. In your example, the declaration of the function returnSeven is in the header file header.h. When you compile main.c, you include the header with the declaration so that the compiler knows that returnSeven exists when it compiles main.c.

在每个编译单元中,必须声明所使用的所有函数,以让编译器知道函数的存在及其参数。在您的示例中,函数returnSeven的声明位于header文件header.h中。当你编译主要。c,您将标头包含在声明中,以便编译器在编译main.c时知道returnSeven存在。

When the linker does its job, it needs to find the definition of each function. Each function has to be defined exactly once in one of the object files - if there are multiple object files that contain the definition of the same function, the linker will stop with an error.

当链接器完成它的工作时,它需要找到每个函数的定义。每个函数都必须在一个对象文件中定义一次——如果有多个对象文件包含相同函数的定义,链接器将以错误停止。

Your function returnSeven is defined in source.c (and the main function is defined in main.c).

函数returnSeven是在源代码中定义的。c(主函数在main.c中定义)。

So, to summarize, you have two compilation units: source.c and main.c (with the header files that it includes). You compile these to two object files: source.o and main.o. The first one will contain the definition of returnSeven, the second one the definition of main. Then the linker will glue those two together in an executable program.

所以,总结一下,你有两个编译单元:源。c和主要。c(包含头文件)。将它们编译为两个对象文件:源文件。o和main.o。第一个是returnSeven的定义,第二个是main的定义。然后链接器将这两个连接在一个可执行程序中。

About linkage:

关于链接:

There is external linkage and internal linkage. By default, functions have external linkage, which means that the compiler makes these functions visible to the linker. If you make a function static, it has internal linkage - it is only visible inside the compilation unit in which it is defined (the linker won't know that it exists). This can be useful for functions that do something internally in a source file and that you want to hide from the rest of the program.

有外部联动和内部联动。默认情况下,函数具有外部链接,这意味着编译器使这些函数对链接器可见。如果你让一个函数是静态的,它有内部链接——它只在定义它的编译单元中可见(链接器不会知道它的存在)。这对于在源文件内部执行某些操作并希望对程序的其他部分进行隐藏的函数是有用的。

#2


25  

The C language has no concept of source files and header files (and neither does the compiler). This is merely a convention; remember that a header file is always #included into a source file; the preprocessor literally just copy-pastes the contents, before proper compilation begins.

C语言没有源文件和头文件的概念(编译器也没有)。这只是一种惯例;请记住,头文件总是包含在源文件中的#;在开始适当的编译之前,预处理程序实际上只是复制粘贴内容。

Your example should compile (foolish syntax errors notwithstanding). Using GCC, for example, you might first do:

您的示例应该编译(尽管有一些愚蠢的语法错误)。例如,使用GCC,您可以先做:

gcc -c -o source.o source.c
gcc -c -o main.o main.c

This compiles each source file separately, creating independent object files. At this stage, returnSeven() has not been resolved inside main.c; the compiler has merely marked the object file in a way that states that it must be resolved in the future. So at this stage, it's not a problem that main.c can't see a definition of returnSeven(). (Note: this is distinct from the fact that main.c must be able to see a declaration of returnSeven() in order to compile; it must know that it is indeed a function, and what its prototype is. That is why you must #include "source.h" in main.c.)

这将分别编译每个源文件,创建独立的对象文件。在此阶段,returnSeven()尚未在main.c中得到解决;编译器只是标记对象文件的方式表明它必须在将来被解析。所以在这个阶段,这不是主要的问题。c看不到returnSeven()的定义。(注:这与main的事实不同。c必须能够看到returnSeven()声明才能编译;它必须知道它确实是一个函数,以及它的原型是什么。这就是为什么您必须#include“source”。c h”)。

You then do:

你做的事:

gcc -o my_prog source.o main.o

This links the two object files together into an executable binary, and performs resolution of symbols. In our example, this is possible, because main.o requires returnSeven(), and this is exposed by source.o. In cases where everything doesn't match up, a linker error would result.

这将两个对象文件链接到一个可执行的二进制文件中,并执行符号解析。在我们的示例中,这是可能的,因为main。o需要returnSeven(),这是由source.o公开的。在一切都不匹配的情况下,会产生链接器错误。

#3


10  

There is nothing magic about compilation. Nor automatic!

编译没有什么神奇之处。还是自动!

Header files basically provide information to the compiler, almost never code.
That information alone, is usually not enough to create a full program.

头文件基本上向编译器提供信息,几乎从不编写代码。这些信息本身通常不足以创建完整的程序。

Consider the "hello world" program (with the simpler puts function):

考虑“hello world”程序(带有更简单的put函数):

#include <stdio.h>
int main(void) {
    puts("Hello, World!");
    return 0;
}

without the header, the compiler does not know how to deal with puts() (it is not a C keyword). The header lets the compiler know how to manage the arguments and return value.

没有header,编译器不知道如何处理put()(它不是C关键字)。头让编译器知道如何管理参数和返回值。

How the function works, however, is not specified anywhere in this simple code. Somebody else has written the code for puts() and included the compiled code in a library. The code in that library is included with the compiled code for your source as part of the compilation process.

然而,在这个简单的代码中,函数的工作方式并没有被指定。其他人已经为put()编写了代码,并将编译后的代码包含在库中。该库中的代码包含了作为编译过程一部分的源代码的编译代码。

Now consider you wanted your own version of puts()

现在考虑您想要自己版本的put ()

int main(void) {
    myputs("Hello, World!");
    return 0;
}

Compiling just this code gives an error because the compiler has no information about the function. You can provide that information

仅仅编译这段代码就会产生错误,因为编译器没有关于这个函数的信息。你可以提供这些信息。

int myputs(const char *line);
int main(void) {
    myputs("Hello, World!");
    return 0;
}

and the code now compiles --- but does not link, ie does not produce an executable, because there is no code for myputs(). So you write the code for myputs() in a file called "myputs.c"

现在的代码编译——但不链接,ie不生成可执行文件,因为myput()没有代码。因此,您将myput()的代码写入一个名为“myputs.c”的文件中

#include <stdio.h>
int myputs(const char *line) {
    while (*line) putchar(*line++);
    return 0;
}

and you have to remember to compile both your first source file and "myputs.c" together.

你必须记住编译你的第一个源文件和“myput”。c”在一起。

After a while your "myputs.c" file has expanded to a hand full of functions and you need to include the information about all the functions (their prototypes) in the source files that want to use them.
It is more convenient to write all the prototypes in a single file and #include that file. With the inclusion you run no risk of making a mistake when typing the prototype.

过了一会儿,你的“米普特人”。c"文件已经扩展到满手的函数,您需要在希望使用它们的源文件中包含关于所有函数(它们的原型)的信息。将所有原型写在一个文件中更方便,#include包含该文件。通过包含,您在输入原型时不会出错。

You still have to compile and link all the code files together though.

但是您仍然需要编译并链接所有的代码文件。


When they grow even more, you put all the already compiled code in a library ... and that's another story :)

当它们增长得更多时,你把所有已经编译好的代码放在一个库中……这是另一个故事

#4


4  

Header files are used to separate the interface declarations which correspond to the implementations in the source files. They're abused in other ways, but this is the common case. This isn't for the compiler, it's for the humans writing the code.

头文件用于分离与源文件中的实现相对应的接口声明。它们在其他方面被滥用,但这是常见的情况。这不是为编译器做的,而是为编写代码的人做的。

Most compilers don't actually see the two files separately, they are combined by the preprocessor.

大多数编译器实际上并没有分别看到这两个文件,它们是由预处理器组合的。

#5


2  

The compiler itself has no specific "knowledge" of relationships between source files and header files. Those types of relationships are typically defined by project files (e.g., makefile, solution, etc.).

编译器本身不知道源文件和头文件之间的关系。这些类型的关系通常由项目文件(例如makefile、解决方案等)定义。

The given example appears as if it would compile correctly. You would need to compile both source files and then the linker would need both object files to produce the executable.

给定的示例看起来似乎可以正确地编译。您需要编译两个源文件,然后链接器需要两个对象文件来生成可执行文件。