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

2024-07-08 1211阅读

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

  • 一、编译环境和运行环境
  • 二、翻译环境
    • 2.1、 预处理
    • 2.2、 编译
      • (1)词法分析
      • (2)语法分析
      • (3)语义分析
      • 2.3、 汇编
      • 2.4、链接
      • 三、运行环境

        一、编译环境和运行环境

          平时我们说写 C语言 代码,写程序,不难发现:其实写出来的都是 t e s t test test. c c c、 t e s t test test. h h h 等源文件和头文件。我们直接打开他们,是可以直接看懂的,这也就说明了他们其实是文本文件。但是计算机是看不懂他们的,计算机只能识别二进制指令,无法对文件中的代码直接执行。这时就需要将 C语言 代码进行处理变成二进制的指令。

          

          而将代码处理成二进制指令正是编译器需要做的事情。

          

        在 ANCI C 的任何一种实现中,存在两个不同的环境:

        1. 翻译环境:在这个环境中源代码被转换成可执行的机器指令(二进制指令)
        2. 执行环境:它用于实际执行代码

          

        二、翻译环境

          那翻译环境是怎么将源代码转换为可执行的机器指令的呢?这里我们就得展开讲解一下翻译环境所做的事情

          

          其实翻译环境是由编译和链接两大过程组成,而编译又可以分解成:预处理(有些书也叫预编译)、编译、汇编三个过程。

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

          

        一个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 是其链接器

            

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

            我们可以在 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 文件:

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

              

            t e s t test test. i i i 文件:

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

               . i .i .i 文件只有输入指令生成后我们才能看到,正常情况下生成中间文件编译器用完就删掉了。

              

              

            2.2、 编译

              编译过程就是将预处理后的文件进行一系列的:词法分析、语法分析、语义分析及优化,生成相应的汇编代码文件。

            编译过程的命令如下:

            gcc -S test.i -o test.s
            

              编译过程最终会生成 .s 的文件,它里面放的是汇编代码。其实编译阶段整体上就是将 C 代码转换成汇编代码

              

            t e s t . s test.s test.s 文件:

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

              那编译过程具体是做了什么工作呢?

              他们分别是:词法分析、语法分析、语义分析及优化

              下面让我们一起简单了解

              

              假设有下面的代码,在进行编译时会时编译器怎么做呢

            array[index] = (index + 4) * (2 + 6);
            

              

            (1)词法分析

              将源代码程序输入扫描器,扫描器的任务就是简单的进行词法分析,把代码中的字符分割成一系列的记号(关键字、标识符、字面量、特殊字符等)。

              

            上述代码进行词法分析后得到了 16 个记号:

            记号类型记号类型
            array标识符4数字
            [左方括号)右圆括号
            index标识符*乘号
            ]右方括号(左圆括号
            =赋值2数字
            (左圆括号+加号
            index标识符6数字
            +加号)右圆括号

              

            (2)语法分析

              接下来则是语法分析。语法分析器会对扫描产生的记号进行语法分析,从而产生语法树。这些语法树是以表达式为节点的树

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

              

            (3)语义分析

              由语义分析器来完成语义分析,即对表达式的语法层面分析。编译器所能做的分析是语义的静态分析。静态语义分析通常包括声明和类型的匹配。这个阶段会报告错误的语法信息

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

              

            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
            //声明外部函数
            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 时,会暂时将其地址搁置。

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

              

              到了编译过程,编译器会将多个目标文件链接在一起(目标文件的格式是一样的,并且是分段的形式),从而生成可执行程序(可执行策划给你续最终也是如目标文件一样的分段形式)

              

              在合并过程中,符号表也需要合并成一份。合并后的符号表每个符号只能有一份,那 Add 和 g_val 符号自然用其有效地址的那一份,这样符号表就完成了合并。通过 A d d Add Add 的地址,就自然而然能找到 A d d Add Add 函数了。

              

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

              而上述对地址的修正过程被叫做:重定位

              

              前面我们非常简洁的讲解了一个 C 的程序是如何编译和链接,到最终生成可执行程序的过程,其实很多内部的细节无法展开讲解。

              比如:目标文件的格式 e l f elf elf,链接底层实现中的空间与地址分配,符号解析和重定位等,如果你有兴趣,可以看 《程序员的自我修养》 一书来详细了解。

              

              

            三、运行环境

              运行环境实际上是非常复杂的,我们这里简单了解了解

            1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手动安排(单片机烧板子),也可以通过可执行代码置入只读内存来完成。
            2. 程序的执行便开始。接着便调用 m a i n main main 函数
            3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(函数栈帧),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
            4. 终止程序。正常终止 m a i n main main 函数;也有可能是意外终止。

              

              

              

              

              


              好啦,本期关于编译和链接的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在C语言的学习路上一起进步!

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]