络腮胡菲菲 发表于 2026-2-27 09:33:10

【C语言】 —— 编译和链接

一、编译环境和运行环境

  平常我们说写 C语言 代码,写步伐,不难发现:实在写出来的都是                                    t                         e                         s                         t                              test                  test.                                 c                              c                  c、                                 t                         e                         s                         t                              test                  test.                                 h                              h                  h 等源文件和头文件。我们直接打开他们,是可以直接看懂的,这也就分析白他们实在是文本文件。但是盘算机是看不懂他们的,盘算机只能辨认二进制指令,无法对文件中的代码直接实行。这时就必要将 C语言 代码举行处理惩罚变成二进制的指令。
  
  而将代码处理惩罚成二进制指令正是编译器必要做的事变。
  
在 ANCI C 的任何一种实现中,存在两个差别的环境:

[*]翻译环境:在这个环境中源代码被转换成可实行的呆板指令(二进制指令)
[*]实行环境:它用于实际实行代码
  
二、翻译环境

  那翻译环境是怎么将源代码转换为可实行的呆板指令的呢?这里我们就得睁开解说一下翻译环境所做的事变
  
  实在翻译环境是由编译和链接两大过程构成,而编译又可以分解成:预处理惩罚(有些书也叫预编译)、编译、汇编三个过程。
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvYjczYmQ4ZmYzMDc4MDY2ZjQ3N2I5ZGU5NWFkY2U4ODcucG5nI3BpY19jZW50ZXI=
  
一个C语言的项目中大概由多个 .                                 c                              c                  c 文件一起构建,那多个 .                              c                        c               c 文件怎样天生可实行步伐呢?


[*]多个 .                                 c                              c                  c 文件单独颠末编译器,编译处理惩罚天生对应的目标文件。
[*]在                                    W                         i                         n                         d                         o                         w                         s                              Windows                  Windows 环境下的目标文件的后缀是 .obj,在                                 L                         i                         n                         u                         s                              Linus                  Linus环境下目标文件的后缀是 .o
[*]多个目标文件和链接库一起颠末毗连器处理惩罚天生终极的可实行步伐。
[*]链接库是指运行时库(它是支持步伐运行的根本函数聚集)、C语言库函数大概第三方库。
  这里需提一下,我们所用的                               V                      S                      2022                        VS2022               VS2022 是一种集成开辟环境,包罗:编辑器、编译器、链接器、调试器。而                               c                      l                        cl               cl.                              e                      x                      e                        exe               exe 是其编译器,                              l                      i                      n                      k                        link               link.                              e                      x                      e                        exe               exe 是其链接器
  
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvMDY4MjFlYzU1NDVjYmIyYzRlODMwNDE1ODk3MmU3MzAucG5nI3BpY19jZW50ZXI=
  我们可以在                               L                      i                      n                      u                      s                        Linus               Linus 服务器、                              g                      c                      c                        gcc               gcc 的环境下对链接和编译各个阶段举行观察
  
  
2.1、 预处理惩罚

  在预处理惩罚阶段,源文件和头文件会被处理惩罚成 .i 为后缀的文件。
  在                               g                      c                      c                        gcc               gcc 中,将 .c 文件处理惩罚成 .h 文件,下令如下:
gcc -E test.c -o test.i
  
  预处理惩罚阶段重要处理惩罚那些源文件中 # 开始的编译指令。好比:#                              i                      n                      c                      l                      u                      d                      e                        include               include,#                              d                      e                      f                      i                      n                      e                        define               define,处理惩罚的规则如下:


[*]将全部的 #                                        d                            e                            f                            i                            n                            e                                  define                     define 删除,睁开全部的宏界说
[*]处理惩罚全部的条件编译指令,如:#if、#ifdef、#elif、#else、#endif
[*]处理惩罚 #                                        i                            n                            c                            l                            u                            d                            e                                  include                     include 预编译指令,将包罗的头文件的内容插入到该预编译指令的位置。这个过程是递归举行的,也就是说被包罗的头文件也大概包罗其他文件。
[*]删除全部表明
[*]添加行号和文件名标识,方便后续编译器天生调试信息等
[*]或保存全部的 #                                        p                            r                            a                            g                            m                            a                                  pragma                     pragma 的编译器指令,编译器后续会利用
  颠末预处理惩罚后的                               .                      i                        .i               .i 文件不再包罗宏界说,由于宏已经被睁开。而且包罗的头文件都被插入到 .i 文件中。以是我们无法知道宏界说大概头文件是否包罗精确的时间,可以检察预处理惩罚后的                               .                      i                        .i               .i 文件来确认。
  
                                    t                         e                         s                         t                              test                  test.                                 c                              c                  c 文件:
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvYmU1NGRlZTJlNGUzNmE2NDNiM2Y2MWExYjRkYThiOWEucG5nI3BpY19jZW50ZXI=
  
                                    t                         e                         s                         t                              test                  test.                                 i                              i                  i 文件:
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvMjZkMTA5Njg1MGQxOGY1NTUzMWFiNGM1NDBlYzkyMGIucG5nI3BpY19jZW50ZXI=
                                .                      i                        .i               .i 文件只有输入指令天生后我们才华看到,正常环境下天生中央文件编译器用完就删掉了。
  
  
2.2、 编译

  编译过程就是将预处理惩罚后的文件举行一系列的:词法分析、语法分析、语义分析及优化,天生相应的汇编代码文件。
编译过程的下令如下:
gcc -S test.i -o test.s
  编译过程终极会天生 .s 的文件,它内里放的是汇编代码。实在编译阶段团体上就是将 C 代码转换成汇编代码
  
                                    t                         e                         s                         t                         .                         s                              test.s                  test.s 文件:
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvYzMxZWFjNzc4ZDBkNWU5ZWFmYWZlYmQyMzkyNTA5M2UucG5nI3BpY19jZW50ZXI=
  那编译过程具体是做了什么工作呢?
  他们分别是:词法分析、语法分析、语义分析及优化
  下面让我们一起简单相识
  
  假设有下面的代码,在举行编译时会时编译器怎么做呢
array = (index + 4) * (2 + 6);
  
(1)词法分析

  将源代码步伐输入扫描器,扫描器的任务就是简单的举行词法分析,把代码中的字符分割成一系列的暗号(关键字、标识符、字面量、特殊字符等)。
  
上述代码举行词法分析后得到了 16 个暗号:
暗号范例暗号范例array标识符4数字[左方括号)右圆括号index标识符*乘号]右方括号(左圆括号=赋值2数字(左圆括号+加号index标识符6数字+加号)右圆括号  
(2)语法分析

  接下来则是语法分析。语法分析器会对扫描产生的暗号举行语法分析,从而产生语法树。这些语法树是以表达式为节点的树
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvYzg5MjQyYmVhYmRjM2QzZDBiMGYxNzVhYTIzNGIxNzcucG5nI3BpY19jZW50ZXI=
  
(3)语义分析

  由语义分析器来完成语义分析,即对表达式的语法层面分析。编译器所能做的分析是语义的静态分析。静态语义分析通常包罗声明和范例的匹配。这个阶段会陈诉错误的语法信息
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvMzkxNjZkNDI3NGQxM2I3MTEyMTMzNWFmNmY5ZjNjMGMucG5nI3BpY19jZW50ZXI=
  
2.3、 汇编

  汇编是指通过汇编器将汇编代码变化成呆板可实行的指令,每一个汇编语句险些都对应一条呆板指令。就是按照汇编指令和呆板指令的对照表逐一的举行翻译,也不做指令优化
  汇编的下令如下:
gcc -c test.s -o test.o
  颠末汇编处理惩罚后文件即为目标文件(                              .                      o                      b                      j                        .obj               .obj /                               .                      o                        .o               .o)目标文件为二进制文件,无法通过文本编辑器打开
  
2.4、链接

  链接是一个复杂的过程,链接的时间必要把一堆文件链接在一起才天生可实行步伐。
  链接的过程重要包罗:地点和空间分配,符号决媾和重定位等这些步调。
  
  链接重要办理的是一个项目中多个文件、多模块之间相互调用的题目。
  好比:如今有两个文件(                              t                      e                      s                      t                      .                      c                        test.c               test.c 和                               a                      d                      d                      .                      c                        add.c               add.c)
  
                               t                      e                      s                      t                      .                      c                        test.c               test.c 文件
#incldue<stdio.h>

//声明外部函数
extern int Add(int x, int y);
//声明外部的全局变量
extern int g_val;

int main()
{
        int a = 10;
        int b = 20;
        int c = Add(10, 20);
        printf("%d\n", c);
        return 0;
}
  
                               A                      d                      d                      .                      c                        Add.c               Add.c 文件
int g_val = 2024;

int Add(int x, int y)
{
        return x + y;
}
  
  为什么在 Add.c 中界说的文件在 test.c 文件中声明一下就可以利用呢?
  这里举行简单的相识
  
  颠末前面的学习,我们知道每一个源文件(                              .                      c                        .c               .c)颠末编译过程后都会天生自己的目标文件(                               .                      o                        .o               .o /                               .                      o                      b                      j                        .obj               .obj )
  
  在编译的过程中,会对代码中的符号举行符号的汇总,并形成相应的符号表,符号表中会存储符号相对应的地点。在产生                               t                      e                      s                      t                      .                      c                        test.c               test.c 文件的符号表时,遇到只有声明而未界说的符号 Add 和 g_val 时,会临时将其地点搁置。
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvMmMyZjE5NjNiMmE2Zjg5YjgyYTcxMWE5MzViZmRhZmUucG5nI3BpY19jZW50ZXI=
  
  到了编译过程,编译器会将多个目标文件链接在一起(目标文件的格式是一样的,而且是分段的情势),从而天生可实行步伐(可实行策划给你续终极也是如目标文件一样的分段情势)
  
  在归并过程中,符号表也必要归并成一份。归并后的符号表每个符号只能有一份,那 Add 和 g_val 符号自然用其有用地点的那一份,如许符号表就完成了归并。通过                               A                      d                      d                        Add               Add 的地点,就自然而然能找到                               A                      d                      d                        Add               Add 函数了。
  
   https://dis.qidao123.com/imgproxy/aHR0cHM6Ly9pLWJsb2cuY3NkbmltZy5jbi9ibG9nX21pZ3JhdGUvY2RkMTcyNTc2MGQwMjI5YjRlZDc2MGE4ZDYzZDA3OGIucG5nI3BpY19jZW50ZXI=
  而上述对地点的修正过程被叫做:重定位
  
  前面我们非常简便的解说了一个 C 的步伐是怎样编译和链接,到终极天生可实行步伐的过程,实在许多内部的细节无法睁开解说。
  好比:目标文件的格式                               e                      l                      f                        elf               elf,链接底层实现中的空间与地点分配,符号剖析和重定位等,如果你有爱好,可以看 《步伐员的自我修养》 一书来具体相识。
  
  
三、运行环境

  运行环境实际上好坏常复杂的,我们这里简单相知趣识

[*]步伐必须载入内存中。在有操纵体系的环境中:一样平常这个由操纵体系完成。在独立的环境中,步伐的载入必须由手动安排(单片机烧板子),也可以通过可实行代码置入只读内存来完成。
[*]步伐的实行便开始。接着便调用                                       m                            a                            i                            n                                  main                     main 函数
[*]开始实行步伐代码。这个时间步伐将利用一个运行时堆栈(函数栈帧),存储函数的局部变量和返回地点。步伐同时也可以利用静态(static)内存,存储于静态内存中的变量在步伐的整个实行过程不绝保存他们的值。
[*]停止步伐。正常停止                                       m                            a                            i                            n                                  main                     main 函数;也有大概是不测停止。
  
  
  
  
  
  好啦,本期关于编译和链接的知识就先容到这里啦,渴望本期博客能对你有所资助。同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!
页: [1]
查看完整版本: 【C语言】 —— 编译和链接