2013-08-31 134 views
18

我知道头文件具有在.c文件中用于调用#include的各种函数,结构等的前向声明,对吧?据我明白了, “三权分立” 发生这样的:C头文件和编译/链接

头文件func.h

  • 包含的函数的前向声明

    int func(int i); 
    

C源文件: func.c

  • 包含实际的函数定义

    #include "func.h" 
    
    int func(int i) { 
        return ++i ; 
    } 
    

C源文件source.c( “实际” 计划):

#include <stdio.h> 
#include "func.h" 

int main(void) { 
    int res = func(3); 
    printf("%i", res); 
} 

我的问题是:看到了#include是一个简单的编译器指令,副本在#include所在文件中的.h的内容,.c文件如何知道如何实际执行该功能?它所得到的是int func(int i);,所以它怎么能实际执行该功能?它如何获得func的实际定义?标题中是否包含某种“指针”,表示“这是我的定义,在那边!”?

它是如何工作的?

+0

这就是'Linker'解决定义并确保你在编译期间声称存在的事实际存在的魔力。 –

+0

在处理头文件时,您可能需要阅读[include guard](http://en.wikipedia.org/wiki/Include_guard)。 –

+0

我知道包括警卫(ifndef所有这些),但为了简洁省略了它们。 – Aristides

回答

20

Uchia Itachi给出了答案。这是链接器

使用GNU C编译器gcc你会编的一档节目像

gcc hello.c -o hello # generating the executable hello 

但是编译在你的例子所描述的两个(或更多)文件的程序,你就必须做到以下几点:

gcc -c func.C# generates the object file func.o 
gcc -c main.C# generates the object file main.o 
gcc func.o main.o -o main # generates the executable main 

每个目标文件都有外部符号(您可能会认为它是公共成员)。函数默认是外部的,而(全局)变量默认为内部函数。你可以通过定义

static int func(int i) { # static linkage 
    return ++i ; 
} 

/* global variable accessible from other modules (object files) */ 
extern int global_variable = 10; 

当遇到调用一个函数,在主模块中没有定义更改此行为,链接器搜索所有的提供的对象文件(库)用于定义被调用函数的模块的输入。默认情况下,你可能有一些库链接到你的程序,这就是你如何使用printf,它已经被编译到一个库中。

如果您真的感兴趣,请尝试一些汇编编程。这些名称与汇编代码中的标签相同。

+0

因此,对于GCC,模式是:1.对每个.c(带有定义)和.h(带有func原型)使用-c标志来创建每个.o 2.使用-o标志和每个.o文件创建最终exe文件? – Aristides

+0

是的。 “-c”选项用于“编译”,因此只需将目标代码编译为目标文件即可。没有-c的gcc认识到输入是目标文件,所以它只是使用链接器将它们链接在一起。最后,-o标志是可选的,它用于指定可执行文件的输出文件名。 –

+2

这是一个非常好的答案,谢谢。 –

14

这是处理所有的链接器。编译器只是在对象文件中发出一个特殊序列,表示“我有这个外部符号func,请解析它”链接器。然后链接程序会看到该内容,并搜索所有其他目标文件和库以查找该符号。

+0

这是否意味着项目中的所有'.c'文件都将被搜索? –

+0

@LidongGuo如果你在命令行上一起编译所有源文件,或者如果你从所有源创建目标文件并将它们链接在一起,那么是的,它们将被搜索。它不是自动完成的,你必须告诉链接器你想链接哪些目标文件,只有那些将被搜索。 –

2

报头提供不仅在同一程序中其它.c文件,但同样的是可以以二进制形式被分发库的访问。一个.c文件与另一个文件的关系与依赖另一个的库完全相同。

由于编程接口需要在文本形式无论执行的格式,头文件是有意义的关注点分离。

正如其他人所说,解析函数调用和库和来源(编译单元)被称为连接器之间的访问节目。

链接器不与接口兼容。它只是在所有翻译单元和库中定义的所有名称的大表,然后将这些名称链接到访问它们的代码行。 C的古代用法甚至允许在没有任何实现声明的情况下调用函数;只是假定每个未定义类型都是int

3

没有相同的编译单元内的定义符号的声明告诉编译器编译一个占位符,该符号的地址转换成目标文件。

链接器将会看到符号的定义是必需的,并且会查找库和其他对象文件中符号的外部定义。

如果链接器找到定义,原始对象文件中的占位符将被最终可执行文件中找到的地址替换。

1

通常当你编译这样的文件:

gcc -o program program.c 

你真的是调用驱动程序,它具有以下功能:

  • 预处理(如果你问它是一个单独的步骤)使用cpp
  • 汇编(可能与预处理集成)使用cc1
  • 汇编,使用as(gas,GNU汇编程序)。
  • 链接使用collect2,它也使用ld(GNU链接器)。

典型地,在第一阶段3中,将创建一个简单的对象文件(扩展名.o),其被通过编译编译单元创建的(即是一个.c文件,与所述的#include和替换其他指示预处理器)。

第四阶段是创建最终可执行文件的阶段。编译完一个单元后,编译器会将多段代码标记为需要由链接器解析的引用。链接器的工作是在许多编译单元中搜索并解析对外部编译单元的引用。