【C语言】编译和链接
前言:
编译和链接是计算机程序开发中的两个重要步骤,用于将源代码转化为可执行的程序。
文章目录
一、翻译环境和运行环境
翻译环境: 是指在开发计算机程序时所使用的工具和设置的集合。它包括开发者用来编写、测试和调试代码的软件工具,,如文本编辑器、集成开发环境(IDE)、编译器、调试器等。
运行环境: 运行环境是指在计算机上执行已编译程序时的环境和设置。
在翻译环境中包含了编译和链接过程:
翻译环境:
⼀个C语⾔的项⽬中可能有多个.c⽂件⼀起构建,那多个.c⽂件如何⽣成可执⾏程序呢?
- 多个.c⽂件单独经过编译出编译处理⽣产对应的⽬标⽂件。
- 注:在Windows环境下的⽬标⽂件的后缀是.obj,Linux环境下⽬标⽂件的后缀是.o
- 多个⽬标⽂件和链接库⼀起经过链接器处理⽣成最终的可执⾏程序。
- 链接库是指运行时库(它是⽀持程序运⾏的基本函数集合)或者第三⽅库。
二、翻译环境中的编译
编译⼜分为:预处理(或预编译)、编译、汇编三个过程。
2.1 预处理(预编译)
预处理是计算机程序编译过程中的第一个阶段,它主要负责对源代码文件进行一些文本处理操作,以准备将源代码转化为目标文件的过程。
- 宏替换:在预处理阶段,C和C++编译器会处理源代码中的宏定义,并将其替换为相应的文本。这可以通过宏展开(Macro Expansion)来实现。
#define PI 3.14159265359
double circle_area = PI * radius * radius;//圆形面积
在预处理之后,上述代码将被替换为:
double circle_area = 3.14159265359 * radius * radius;
- 头文件包含:#include 预处理指令用于包含其他头文件中的代码,以便在源代码文件中使用其定义的函数和变量。例如:
#include <stdio.h>
预处理会将 <stdio.h> 中的内容插入到当前源代码文件中。
- 条件编译:预处理允许使用条件编译指令,如 #ifdef、#ifndef、#if、#else 和 #endif,根据条件编译不同的代码块。在下一篇博客中会仔细讲解预处理指令。
- 移除注释:预处理器通常会移除源代码中的注释,以减小目标文件的大小。在预处理之后,注释将被完全移除。
- 符号替换:预处理还可以执行符号替换,将定义的符号替换为其对应的值。宏替换和符号替换与不同之处在于,符号替换指的是常量宏(只能是常量),而宏替换指的是可以代表更复杂的代码块以及常量。
#define MAX_VALUE 100//MAX_VALUE
int value = MAX_VALUE;
//预处理会将 MAX_VALUE 替换为 100
2.2 编译
编译过程中的词法分析、语法分析、语义分析和代码生成是编译器中的核心步骤,这些步骤将源代码转化为机器可执行的目标文件。
假如有以下一段代码:
array[index] = (index+4)*(2+6);
2.2.1 语法分析
在词法分析阶段,编译器会将源代码分解成词法单元(令牌)。
上⾯程序进⾏词法分析后得到了16个词法单元:
词法单元 | 类型 |
---|---|
array | 标识符 |
[ | 左方括号 |
index | 标识符 |
] | 右方括号 |
= | 赋值运算符 |
( | 左括号 |
index | 标识符 |
+ | 加法运算符 |
4 | 整数常量 |
) | 右括号 |
* | 乘法运算符 |
( | 左括号 |
2 | 整数常量 |
+ | 加法运算符 |
6 | 整数常量 |
) | 右括号 |
; | 分号 |
2.2.2 语法分析
接下来语法分析器,将对扫描产⽣的记号进⾏语法分析,从⽽产⽣语法树。这些语法树是以表达
式为节点的树。
2.2.3 语义分析
语义分析阶段检查代码中的语义错误和类型匹配。
对于这个代码片段,它可能执行以下检查:
- 检查 array 和 index 是否已经声明。
- 检查 index 是否为整数类型(因为它用于数组索引)。
- 检查 index+4 和 2+6 的结果是否是整数类型。
- 检查 array 是否支持索引操作。
- 检查赋值运算的左边和右边的类型是否兼容。
- 如果发现任何类型不匹配或未声明的变量,将生成相应的语义错误。
2.3 汇编
在这个阶段,编译器将生成的中间表示(通常是汇编代码)转化为目标机器的二进制机器代码,这个二进制代码可以在计算机上执行。
三、翻译环境中的链接
在编程中,链接是将多个目标文件或库文件合并成一个可执行文件或共享库的过程。链接是编译过程的最后一步,它将不同的目标文件和库文件整合在一起,以创建最终可运行的程序或共享库。
步骤包括:目标文件生成、地址和空间分配,符号决议和重定位等这些步骤。具体的不再深入讲解
四、运行环境
- 程序必须载⼊内存中。在有操作系统的环境中:⼀般这个由操作系统完成。在独⽴的环境中,程序的载⼊必须由手工安排,也可能是通过可执行代码置⼊只读内存来完成。
- 程序的执行便开始。接着便调⽤main函数。
- 开始执行程序代码。这个时候程序将使⽤⼀个运⾏时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使⽤静态(static)内存,存储于静态内存中的变量在程序的整个执⾏过程⼀直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
如果你喜欢这篇文章,点赞?+评论+关注⭐️哦!
欢迎大家提出疑问,以及不同的见解。