C 中的头文件和源文件如何工作?
我仔细研究了可能的重复项,但没有一个答案能被理解。
tl;dr:C
中源文件和头文件如何相关?项目是否在构建时隐式地整理声明/定义依赖关系?
我试图了解编译器如何理解 .c
和 之间的关系.h
文件。
给定这些文件:
header.h:
int returnSeven(void);
source.c:
int returnSeven(void){
return 7;
}
main.c:
#include <stdio.h>
#include <stdlib.h>
#include "header.h"
int main(void){
printf("%d", returnSeven());
return 0;
}
这个混乱会编译吗?我目前正在 NetBeans 7.0 中使用 Cygwin 的 gcc 进行工作,它可以自动执行大部分构建任务。当项目编译时,涉及的项目文件是否会根据 header.h
中的声明来整理 source.c
的隐式包含?
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?
I'm trying to understand how the compiler understands the relationship between .c
and .h
files.
Given these files:
header.h:
int returnSeven(void);
source.c:
int returnSeven(void){
return 7;
}
main.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
?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
将 C 源代码文件转换为可执行程序通常分两步完成:编译和链接。
首先,编译器将源代码转换为目标文件 (
*.o
)。然后,链接器获取这些目标文件以及静态链接库并创建可执行程序。第一步,编译器采用一个编译单元,它通常是一个预处理的源文件(因此,一个源文件包含它
#include
的所有头文件的内容s) 并将其转换为目标文件。在每个编译单元中,必须声明使用的所有函数,以让编译器知道该函数存在及其参数是什么。在您的示例中,函数
returnSeven
的声明位于头文件header.h
中。编译main.c
时,将标头包含在声明中,以便编译器在编译main.c
时知道returnSeven
存在。当链接器完成其工作时,它需要找到每个函数的定义。每个函数必须在一个目标文件中精确定义一次 - 如果有多个目标文件包含同一函数的定义,则链接器将停止并出现错误。
您的函数
returnSeven
在source.c
中定义(main
函数在main.c
中定义)。因此,总而言之,您有两个编译单元:
source.c
和main.c
(及其包含的头文件)。您将它们编译为两个目标文件:source.o
和main.o
。第一个包含returnSeven
的定义,第二个包含main
的定义。然后链接器会将这两者粘合在一起形成一个可执行程序。关于联动:
有外部联动和内部联动。默认情况下,函数具有外部链接,这意味着编译器使这些函数对链接器可见。如果您将函数设为静态,则它具有内部链接 - 它仅在定义它的编译单元内可见(链接器不会知道它存在)。这对于在源文件内部执行某些操作并且您希望对程序的其余部分隐藏的函数非常有用。
Converting C source code files to an executable program is normally done in two steps: compiling and linking.
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.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
#include
s) and converts that to an object file.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 fileheader.h
. When you compilemain.c
, you include the header with the declaration so that the compiler knows thatreturnSeven
exists when it compilesmain.c
.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 insource.c
(and themain
function is defined inmain.c
).So, to summarize, you have two compilation units:
source.c
andmain.c
(with the header files that it includes). You compile these to two object files:source.o
andmain.o
. The first one will contain the definition ofreturnSeven
, the second one the definition ofmain
. Then the linker will glue those two together in an executable program.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.C语言没有源文件和头文件的概念(编译器也没有)。这只是一个惯例;请记住,头文件始终被
#include
包含到源文件中;在正确的编译开始之前,预处理器实际上只是复制粘贴内容。您的示例应该编译(尽管存在愚蠢的语法错误)。例如,使用 GCC,您可能首先会这样做:
这会单独编译每个源文件,创建独立的目标文件。在此阶段,
returnSeven()
尚未在main.c
内得到解析;编译器只是以某种方式标记了目标文件,表明将来必须对其进行解析。所以在这个阶段,main.c
看不到returnSeven()
的定义并不是问题。 (注意:这与main.c
必须能够看到returnSeven()
的声明才能编译这一事实不同;它必须知道它确实是一个函数,以及它的原型是什么,这就是为什么您必须在main.c
中#include "source.h"
。)这样做:
这将两个目标文件链接在一起可执行二进制文件,并执行符号解析。在我们的示例中,这是可能的,因为
main.o
需要returnSeven()
,而这是由source.o
公开的。如果所有内容都不匹配,则会导致链接器错误。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
#include
d into a source file; the preprocessor literally just copy-pastes the contents, before proper compilation begins.Your example should compile (foolish syntax errors notwithstanding). Using GCC, for example, you might first do:
This compiles each source file separately, creating independent object files. At this stage,
returnSeven()
has not been resolved insidemain.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 thatmain.c
can't see a definition ofreturnSeven()
. (Note: this is distinct from the fact thatmain.c
must be able to see a declaration ofreturnSeven()
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"
inmain.c
.)You then do:
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
requiresreturnSeven()
, and this is exposed bysource.o
. In cases where everything doesn't match up, a linker error would result.编译并没有什么神奇之处。也不是自动的!
头文件基本上向编译器提供信息,几乎从不编码。
仅凭这些信息通常不足以创建完整的程序。
考虑“hello world”程序(带有更简单的
puts
函数):如果没有标头,编译器不知道如何处理
puts()
(它不是一个C 关键字)。标头让编译器知道如何管理参数和返回值。然而,这个简单的代码中没有指定该函数的工作原理。其他人已经编写了 puts() 代码并将编译后的代码包含在库中。作为编译过程的一部分,该库中的代码包含在源的已编译代码中。
现在考虑一下您想要自己的
puts()
版本,仅编译此代码会出现错误,因为编译器没有有关该函数的信息。您可以提供该信息
,代码现在可以编译 --- 但不会链接,即不会生成可执行文件,因为没有
myputs()
的代码。因此,您在名为“myputs.c”的文件中编写myputs()
的代码,并且必须记住编译两者您的第一个源文件和“myputs.c”一起。
一段时间后,您的“myputs.c”文件已扩展为一大堆函数,您需要在想要使用它们的源文件中包含有关所有函数(其原型)的信息。
将所有原型写入一个文件并
#include
该文件会更方便。通过包含在内,您在输入原型时不会有犯错误的风险。不过,您仍然需要编译所有代码文件并将其链接在一起。
当它们变得更多时,您将所有已编译的代码放入库中......那是另一个故事了:)
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):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.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.Now consider you wanted your own version of
puts()
Compiling just this code gives an error because the compiler has no information about the function. You can provide that information
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 formyputs()
in a file called "myputs.c"and you have to remember to compile both your first source file and "myputs.c" together.
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.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 :)
头文件用于分隔与源文件中的实现相对应的接口声明。他们还以其他方式受到虐待,但这是常见的情况。这不是为编译器准备的,而是为编写代码的人准备的。
大多数编译器实际上不会单独看到这两个文件,它们是由预处理器组合在一起的。
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.
编译器本身对源文件和头文件之间的关系没有特定的“知识”。这些类型的关系通常由项目文件(例如,makefile、解决方案等)定义。
给定的示例看起来好像可以正确编译。您需要编译两个源文件,然后链接器需要两个目标文件来生成可执行文件。
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.).
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.