机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?

目录

前言

程序的翻译环境和执行环境

1.1翻译环境

1.1.1预处理

1.1.2编译

1.1.3汇编

1.1.4链接

1.2运行环境


前言

初学习编程的大家,一定会有这么一个疑问:我所编写的程序是如何从自己编写的c文件变成一个可执行exe程序的呢?今天这篇博客就来解决大家这个疑问。

程序的翻译环境和执行环境

ANSIC 的任何一种实现中,都存在两个不同的环境:
  1. 第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
  2. 第2种是执行环境,它用于实际执行代码。

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第1张图片

1.1翻译环境

翻译环境可以分为编译链接两大部分,而编译又可继续细分为预处理,编译,汇编三部分

1.1.1预处理

为了搞清楚预处理阶段到底发生了什么,我们使用Linux环境下的gcc编译器去探究,因为在gcc编译器中我们可以更方便地看到程序底层的东西。

 首先我们先编写一个这样的test.c程序:

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第2张图片

 在运行窗口中输入以下指令,可以对test.c进行预编译操作,预编译生成的文件为test.i

gcc - E test.c - o test.i

打开该文件:

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第3张图片

 可以发现文件有以下变化:

  1. 头文件被展开。文件变成了八百多行,因为stdio.h里的代码被展开增加了代码的行数
  2. 删除注释。可以看到Add函数的注释被删除
  3. 宏定义被替换。宏定义参数M被替换成了100

以上三件事就是程序的预处理要做的事,都是在对test.c进行一些文本操作,实际上并没有运行该代码。

1.1.2编译

 在运行窗口中输入以下指令,可以对test.i进行编译操作,编译生成的文件为test.s

gcc - S test.i -o test.s

打开test.s: 

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第4张图片

可以看到这是一段汇编代码,因此编译的作用就是将C语言代码转换为汇编代码,这个过程其实有以下四个步骤:

  • 语法分析
  • 词法分析
  • 语义分析
  • 符号汇总

对编译的具体过程感兴趣的可以选择看《程序员的自我修养这本书》,里面很详细的讲解了这一过程。

1.1.3汇编

 在运行窗口中输入以下指令,可以对test.s进行编译操作,编译生成的文件为test.o(linux环境生成的文件是.o,windows环境生成的是.obj文件)

gcc -c test.s -o test.o 

打开test.o:

发现这是乱码,为什么呢?因为在linux环境下.o文件的文件格式是elf,我们使用如下代码,可以解析elf格式的文件:

readelf -s test.o

 可以看到test.o已经被解析成了二进制代码:

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第5张图片

汇编这一步骤的作用其实就是将我们的汇编代码转化为计算机可以识别的二进制代码,并且生成符号表,符号表是什么呢?在汇编这一过程中,我们的每个C文件中的函数都会生成一个符号表,里面记录了函数的名称和地址,例如我们test.c和add.c就生成了如下的符号表:

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第6张图片

1.1.4链接

先介绍链接这一步的作用,其实就是将我们的test.o变为可执行程序exe文件,并且链接这一步还有两个功能:

  • 第一是合并段表
  • 第二是对符号表的合并和重定位

合并段表是什么意思呢?

刚刚我们提到了汇编生成的.o文件是elf格式的,elf格式通俗来讲其实就是将我们的程序分成了各个段,每一段有自己特殊的意义。

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第7张图片

符号表的合并和重定位又是什么意思呢?

在汇编过程中,我们其实对每个C文件中的函数都生成了一个符号表,那么在链接过程中,我们就要将刚刚生成的符号表进行合并,并且对同名的函数要进行判定判定这个同名函数的地址哪一个才是被定义的函数的地址,并把这个有效地址作为合并后这个函数的地址

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第8张图片

 我们在windows环境中将add.c注释掉,并运行程序会发生什么呢?

机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第9张图片

 机械转码日记【8】程序的编译和链接——从test.c到test.exe都发生了什么?你都懂了吗?_第10张图片

可以看到我们报了一个add未定义的错误,并且它的错误代码是LNK2019,说明我们的错误是在链接过程中出现的,此时函数表中都有add函数和main函数的地址,但是add函数的地址此时是无效的,因为这个函数没有定义。这就说明多个文件进行链接的时候会通过符号表查看来自外部的符号是否真实存在。这其实也可以解释为什么我们没有声明函数,而只是定义了函数,程序也可以正常运行。只要符号表中有这个函数,并且函数地址是正确有效的,程序就可以正常运行。

1.2运行环境

运行环境就是我们的exe文件执行的环境,程序执行的过程如下:

  1. 程序必须载入内存中 。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排(例如嵌入式中给单片机烧录程序),也可能是通过可执行代码置入只读内存来完成。
  2. 程序载入内存后便开始执行,接着便调用main函数。
  3. 开始执行程序代码 。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static )内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
  4. 终止程序。这个过程可能是正常终止的,也可能是非正常终止的(比如断电,栈溢出)。

你可能感兴趣的:(机械转码日记,c语言,开发语言,linux)