【C语言】编译和链接

前言

编译和链接的内容非常复杂深奥,这里只是笼统的讲解。

翻译环境和运行环境

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

第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令(二进制指令)。

第2种是执行环境,它用于实际执行代码。

怎么理解? C语言的代码是文本信息,比如#include<stdio.h>,本质是字母组成的文本信息。要给计算机下达指令去执行,计算机只能识别二进制信息

就像现实中一个日本人只懂日语,一个韩国人只懂韩语,他们在交流时就需要翻译。这个翻译可以将日语翻译为韩语,也可以将韩语翻译为日语。

为了让计算机知道我们下达的是什么指令,需要将我们的代码翻译成二进制。这个过程我们依赖的环境就是翻译环境。在这个环境中源代码会被转换成可执行的机器指令。(二进制指令)

执行环境就在我们的计算机上。

每一个.c文件都要经过编译处理生成自己的目标文件,目标文件经过链接生成可执行程序,然后运行出来。可执行程序就是我们看到的**.exe**文件。

翻译环境

翻译环境是怎么将源代码转换为可执行的机器指令的呢?为了了解这一点,就展开讲一下翻译环境所做的事:

翻译环境是由编译和链接两个大的过程组成的,而++编译又可以分解为:预处理(或预编译)、编译、汇编三个过程。++

.obj 结尾的就是目标文件。 从test.c经过编译器处理生成.obj文件的过程就是编译过程,之后就是链接过程。每个.c文件都会单独经过编译器处理。

在vs中, 编译器叫cl.exe,链接器叫link.exe。在Everything中可以找到:

测试每个.c文件生成目标文件:

运行之后来到test_6_6>test_6_6>x64>Debug文件夹,发现确实多出了test.obj与add.obj两个目标文件。

这些目标文件再经过链接处理,生成可执行程序:来到test_6_6>x64>Debug文件夹,发现有.exe文件生成。

像visual studio这样的IDE,将这些细节隐藏了起来。所以我们不会去关注编译、链接等细节。

注意:在Windows环境下的目标文件的后缀是.obj,Linux环境下目标文件的后缀是.o。

多个目标文件和链接库一起经过链接器处理生成最终的可执行程序。

链接库是指运行时库(它是支持程序运行的基本函数集合)或者第三方库。

比如我们在使用printf这样的函数时,就需要链接库。

如果再把++编译器展开成3个过程++,就是下图的过程:

预处理

该阶段主要处理那些源文件中#开始的预编译指令。比如:#include,#define。处理的规则如下:

--将所有的#define删除,并展开所有的宏定义(替换)。

--处理所有的条件编译指令,如#if #ifdef #elif #else #endif

--处理#include预编译指令,将包含的头文件的内容插入到该预编译指令的位置。这个过程是递归进行的,也就是说被包含的头文件也可能包含其他文件。

--删除所有的注释(变为空格)。

--添加行号和文件名标识,方便后续编译器生成调试信息等。

--保留所有的#pragma的编译器指令,编译器后续会使用。

总结下来都是一些++文本操作++。

经过预处理后的.i文件中不再包含宏定义,因为宏已经被展开,并且包含的头文件都被插入到.i文件中,所以当我们不知道宏定义或头文件是否包含正确时,可以查看预处理后的.i文件来确认。

编译

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

词法分析

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

比如这段代码:

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

进行词法分析后就是:

语法分析

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

语义分析

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

汇编

汇编器是将汇编代码转变成机器可执行的指令,每一个汇编语句几乎都对应一条机器指令。就是根据汇编指令和机器指令的++对照表++一一地进行翻译,也不做指令优化。

其实还会做一件事,就是形成一个符号表。也就是将全局的符号的名字汇总下来,举例:

对于这个.c文件,符号表中又Add 以及这个函数的地址0x100(假设的)。

而对于下面这个.c文件:

符号表中有main及其地址0x104(假设),Add及其地址0x000(无效地址)。局部变量是在程序运行起来才有的,这个阶段是根本没有a b c这些变量的。

最终完成的是将汇编代码翻译成二进制(机器)指令。最后生成目标文件,以.o为后缀。(每个.c文件都有一个)

链接

是一个相对复杂的过程,要将一堆文件链接在一起生成最后的可执行文件。

链接过程主要包括:地址和空间分配,符号决议和重定位这些步骤。

链接解决的是一个项目中多文件、多模块之间互相调用的问题。

记得吗,不同文件在链接后要生成++一个++ 可执行程序。所以在链接阶段,会有符号表的合并和重定位。回到上面两张图的例子,两个.c文件符号表里都有Add,首选哪个地址呢?刚才说了test.c里的Add的地址是无效地址,所以编译器决议后选择留下0x100地址。这就是合并,冲突时决议看保留哪个地址。这时生成的新的符号表就在可执行程序中。

当我们想要调用Add函数时就查这个表得到Add地址,就可以调用这个函数了。

假如这时我们在test.c中将Add误写为了add,那么在链接合并符号表时Add add地址都被保留,add的是无效地址,当我们调用时根据这个地址找不到我们要的函数。

所以,在外部文件中定义的全局变量,使用extern,也可以使用。

目标文件和可执行程序的格式其实是一样的,文件的组织形式是一样的,在Linux底下用的都叫elf的格式...

运行环境

(生成可执行程序后,要依赖运行环境将程序运行起来。这个过程其实也是比较复杂的,这里笼统地将。)

1.程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。(比如单片机的设备里面可能是没有操作系统的。)

2.程序的执行便开始,接着便调用main函数。

3.开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。

4.终止程序。正常终止main函数;也有可能是意外终止(断电了、程序卡死奔溃了等)。

(操作系统代劳)

到此,本文结束,祝阅读愉快^_^

相关推荐
wrx繁星点点3 分钟前
事务的四大特性(ACID)
java·开发语言·数据库
不写八个10 分钟前
Python办公自动化教程(005):Word添加段落
开发语言·python·word
HEX9CF14 分钟前
【CTF Web】Pikachu xss之href输出 Writeup(GET请求+反射型XSS+javascript:伪协议绕过)
开发语言·前端·javascript·安全·网络安全·ecmascript·xss
赵荏苒39 分钟前
Python小白之Pandas1
开发语言·python
丶Darling.41 分钟前
代码随想录 | Day26 | 二叉树:二叉搜索树中的插入操作&&删除二叉搜索树中的节点&&修剪二叉搜索树
开发语言·数据结构·c++·笔记·学习·算法
人生の三重奏1 小时前
前端——js补充
开发语言·前端·javascript
平凡的小码农1 小时前
JAVA实现大写金额转小写金额
java·开发语言
yttandb1 小时前
重生到现代之从零开始的C语言生活》—— 内存的存储
c语言·开发语言·生活
我明天再来学Web渗透1 小时前
【hot100-java】【二叉树的层序遍历】
java·开发语言·数据库·sql·算法·排序算法
结衣结衣.2 小时前
python中的函数介绍
java·c语言·开发语言·前端·笔记·python·学习