首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >C中的头文件和源文件是如何工作的?

C中的头文件和源文件是如何工作的?
EN

Stack Overflow用户
提问于 2011-05-05 21:52:48
回答 5查看 76.8K关注 0票数 71

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

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

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

鉴于这些档案:

header.h

代码语言:javascript
运行
复制
int returnSeven(void);

Sourcee.c

代码语言:javascript
运行
复制
int returnSeven(void){
    return 7;
}

main.c

代码语言:javascript
运行
复制
#include <stdio.h>
#include <stdlib.h>
#include "header.h"
int main(void){
    printf("%d", returnSeven());
    return 0;
}

这个烂摊子会整理吗?我目前正在使用NetBeans 7.0和Cygwin的gcc一起工作,这将自动完成大部分构建任务。在编译项目时,所涉及的项目文件是否会根据source.c中的声明来排序这个隐式包含的header.h

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2011-05-05 22:16:34

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

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

在第一步中,编译器接受一个编译单元,它通常是一个预处理的源文件(所以,一个源文件,其中包含它#include的所有标头的内容),并将其转换为一个对象文件。

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

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

您的函数returnSevensource.c中定义( main函数在main.c中定义)。

总之,您有两个编译单元:source.cmain.c (其中包含头文件)。您可以将这些编译成两个对象文件:source.omain.o。第一个将包含returnSeven的定义,第二个将包含main的定义。然后链接器将这两者结合在一个可执行程序中。

关于联系:

外部连锁内部连锁。默认情况下,函数具有外部链接,这意味着编译器使这些函数对链接器可见。如果您创建了一个函数static,它就有内部链接--它只在定义它的编译单元内可见(链接器不会知道它的存在)。这对于在源文件中执行内部操作并且希望对程序的其余部分进行隐藏的函数非常有用。

票数 99
EN

Stack Overflow用户

发布于 2011-05-05 21:57:08

C语言没有源文件和头文件的概念(编译器也没有)。这只是一种约定;请记住,头文件总是#included到源文件中;预处理器实际上只是复制粘贴内容,然后才开始正确的编译。

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

代码语言:javascript
运行
复制
gcc -c -o source.o source.c
gcc -c -o main.o main.c

这将分别编译每个源文件,创建独立的对象文件。在这个阶段,returnSeven()还没有在main.c中被解析;编译器只是以一种声明将来必须解析它的方式标记对象文件。因此,在这个阶段,main.c看不到returnSeven()的定义并不是一个问题。(注意:这与main.c必须能够看到returnSeven()声明才能编译的事实不同;它必须知道它确实是一个函数,以及它的原型是什么。这就是为什么您必须使用#include "source.h" in main.c)。)

然后你就会:

代码语言:javascript
运行
复制
gcc -o my_prog source.o main.o

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

票数 39
EN

Stack Overflow用户

发布于 2011-05-05 22:25:17

编译没有什么魔力。也不是自动的!

头文件基本上向编译器提供信息,几乎从不编码。

仅凭这些信息通常不足以创建完整的程序。

考虑一下"hello“程序(带有更简单的puts函数):

代码语言:javascript
运行
复制
#include <stdio.h>
int main(void) {
    puts("Hello, World!");
    return 0;
}

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

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

现在考虑一下您想要您自己版本的puts()

代码语言:javascript
运行
复制
int main(void) {
    myputs("Hello, World!");
    return 0;
}

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

代码语言:javascript
运行
复制
int myputs(const char *line);
int main(void) {
    myputs("Hello, World!");
    return 0;
}

代码现在编译--但不链接,即不产生可执行文件,因为没有myputs()的代码。因此,您可以在名为"myputs.c“的文件中为myputs()编写代码。

代码语言:javascript
运行
复制
#include <stdio.h>
int myputs(const char *line) {
    while (*line) putchar(*line++);
    return 0;
}

您必须记住一起编译--同时编译第一个源文件和"myputs.c“。

过了一段时间,您的"myputs.c“文件已经扩展到一只满是函数的手,您需要在要使用它们的源文件中包含关于所有函数(它们的原型)的信息。

在一个文件中编写所有原型和#include更方便。有了包含,您就不会在输入原型时出错。

不过,您仍然必须编译并链接所有代码文件。

当它们越来越多的时候,你会把所有已经编译好的代码放在一个库里.这是另一个故事:)

票数 15
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5904530

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档