一个.c文件在ctrl+F5后如何变成了.exe文件呢?中间经历了什么操作,是谁调用了它,学习其中的原理对编程的学习和理解有不小的提升,如果你和我一样也是学C/C++的,对后面C++和Linux的学习也会有帮助。
1.程序的翻译环境和执行环境
想要学习这块内容,先要搞清楚一个C语言的代码程序在编译运行时的两个环境:
在ANSI C的任何一种实现中,存在两个不同的环境。:1.翻译环境,在这个环境中源代码被转换为可执行的机器指令。 2.执行环境,它用于实际执行代码。
在学C语言后期时候会试着写一些比较“正规”的程序,例如.c文件要和.h文件分开,接口的实现和声明要分开来,那么这些.c文件就是经过编译器的编译生成目标文件再加上链接库链接器生成最后的可执行程序的。
这就是一个源文件在按下ctrl+F5后会经历的操作,在这两个环境下一步一步生成.exe文件。
那么上文提到的链接库又到底是什么东西呢?例如我们可以打开MSDN查询一个经常使用的printf
会发现下面的Libraries(库),说明printf被包含在这些库中,是由C语言提供的,在程序进行编译链接时链接库就是将每个函数需要的库包含进去,这里的printf只是一个例子。
有了这些大概的轮廓后,我们再进行下一步详细的介绍:
由于Viusal Studio属于集成开发环境,所以对于文件生成可执行程序的细节并不容易让我们观察到,但是可以在Linux环境下使用gcc(C/C++编译器)观察到这些细节(关于Linux的知识类博文会在后面和C++交叉展开,这里只是为了理解这里的知识所做辅助,不会展开讲)。
先在Linux下输入以下代码,并且执行它。
执行后会发现在之前的基础上多了一个.out文件,这就是编译完test.c文件后在Linux默认生成的可执行文件。(和Windows下生成.exe文件类似)
这时如果执行a.out文件(a是文件的名字,可以更改)的话,我们想要的答案就会打印在屏幕上
但是这个操作未免也太快了,直接把预编译编译链接汇编一口气完成,我们想观察到具体的细节,有一个选项可以让预编译停下来
直到最后838行之后才是刚刚写的代码,可以发现1~837都是在预编译阶段的操作,一个不足5行的代码预编译阶段竟然会产生八百多行的代码,那么接下来如果我们去掉printf的代码再次生成呢?
代码一下就减少了八百行,可以推断出刚刚的八百多行的代码其中绝大多数都是因为printf一句代码而产生的,那究竟是为什么呢?是否和刚刚包含的头文件有关系呢?答案是肯定的,当#include包含了一个头文件时,其实是包含了
我们可以再举一个例子来理解这个问题,我们将自己写一个头文件,里面只是简单地声明一个刚刚的Add函数,这时再做刚刚的操作,观察预编译阶段会发生什么。
可以发现在刚刚不包含头文件的基础上多了一句Add函数的声明,而这个声明就是我们自己写的头文件,在预编译阶段将它展开,头文件的内容就会暴露出来。
所以在预编译阶段可以下的结论:#include #define #pragma等预处理指令都是在预编译阶段完成,当你的程序中有这些预处理指令时,在预编译阶段就会对他们进行展开,包含等。
例如我用define定义一个全局变量:#define MAX 100 在预编译阶段会自动将MAX所赋的值直接替换成100。
如果在程序中加上一句注释的话,在与编译阶段也会直接对其进行清除。
总结:在预编译阶段所做的操作:
1.包含头文件 2.替换#define定义的标识符 3.删除注释
预编译结束以后进入到编译阶段,那么一个test.c文件的编译可以通过下面的图片理解:
所以在编译阶段编译器会讲.c文件进行语法分析、词法分析、语义分析和符号汇总来将.c文件中的C语言代码转换为汇编代码。
语法分析、语义分析、词法分析又是比较深入的知识了,可能是额外要进行学习的内容,所以在这里并不多说,说白了就是讲编译器是如何认识通过C语言转换来的汇编代码,那么符号汇总其实就是讲一个一个关键字进行拆分,并且将关键字提取出来供编译器识别。
那么编译阶段结束以后,就进入到汇编阶段了,预编译阶段将test.c->test.i,编译阶段将test.i->test.s,汇编阶段就会生成.o文件了,类似于windows中的.obj文件。如果尝试打开test.o文件能否看懂其中的内容呢?
因为是以文本的方式打开,而test.o中存放的是二进制的内容,所以看不懂。所以汇编阶段也就是将汇编代码转换为了二进制指令(机器指令),并且生成了一张符号表,其中存放的就是刚刚符号汇总出的函数的名称和其对应的地址,那么这些.c文件生成后的.o文件如果再执行的话就要到执行链接步骤了。
那么链接阶段所要做的操作有两个:1.合并段表 2.符号表的合并和符号表的重定位
在汇编阶段生成的符号表的格式是elf格式的,当用指令打开这个段表时,文件中的函数、全局变量等就会抽取出来。
比如除了一格main函数外如果再定义一个Add函数,再定义一个全局变量g_val,那么类似于它们的关键字就会展现出来。
将每个.c文件通过符号表的连接和重定位后会生成一个.out文件,最后再运行这个文件即可。