目录
写出test.c这样的文件,为什么就可以运行产生想要的结果如加,减、乘、除等?
其实一个C语言程序test.c最终会编译(编译器经过复杂的处理)成test.exe这样的文件,该文件是可执行文件,这个文件才会运行产生我们想要的效果。
那么一个.c代码的文件到底是怎么产生.exe这样的可执行程序呢?这些动作又有什么意义呢?
此博文探索的就是程序的整个编译过程——程序的环境和预处理。
在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
第2种是执行环境,它用于实际执行代码。
理解:
ANSI C是遵循美国国家标准总局定义的C语言标准的C语言,它的任何一种编译器的实现中,都存在两个不同的环境。即任何一个C语言程序它都有两个环境,分别是翻译环境和执行环境。
支持C语言就需要有编译器,不同的厂商会造成不同的编译器,如VS,gcc,clong这些编译器。
如果编译器支持C而且又遵循ANSI C的标准,则这个编译器就必须存在两个不同的环境——翻译环境和运行环境。
当写出一个.c代码文件时,.c要变成.exe可执行程序,需要经过一个翻译环境,当产生可执行程序时则需要运行才产生我们想要的效果,这里依赖运行环境。
test.exe文件是二进制文件,放的都是二进制的指令,也称机器指令,机器能够识别和读懂的是二进制;而test.c中放的是C语言的源代码,是肉眼可以读懂的,翻译环境就是把C语言的这样的源代码翻译成机器能够读懂的二进制指令;当翻译成二进制指令或机器指令的时候再由运行环境去处理二进制指令产生我们想要的结果。
即C语言代码能被运行产生结果的大概逻辑——一个C语言的源代码经过翻译环境翻译成二进制指令、然后二进制指令经过运行环境,即计算机的运行,计算机能够识别二进制,对二进制指令进行解读和理解、运行就会产生想要我们想要的结果。
翻译环境又被拆分为两个大的环境,(一个翻译环境可以解读为两个过程)分别是编译和链接。编译过程中依赖的工具是编译器;链接过程中依赖的工具是链接器。
C语言代码经过编译处理会生成一个目标文件,而目标文件又经过链接才生成可执行程序。
目标文件是什么?
代码在编译之后按Ctrl+F7就会生成目标文件(.obj文件,在Windows环境中以.obj为后缀的就是目标文件),在本程序路径下的Debug文件夹下。
若工程中有test.c,add.c、sub.c、mul.c、div.c……等很多.c文件,这么多的.c文件都会单独经过编译器处理,test.c经过编译器处理会生成一个test.obj文件;add.c经过编译器处理会生成一个add.obj文件;sub.c经过编译器处理会生成一个sub.obj文件;mul.c经过编译器处理会生成一个mul.obj文件;div.c经过编译器处理会生成一个div.obj文件。
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人
的程序库,将其需要的函数也链接到程序中。
即链接过程中(目标文件加上链接库,链接库也要被链接进去),每一个源文件都会经过编译器单独处理最后生成各自的独立的目标文件,链接器就会把生成的这些目标文件和链接库链接在一起生成.exe可执行程序。
其实只有一个编译器,只不过所有的源文件都会经过编译器处理。
链接库是什么?
写代码时如写的printf()函数是C语言提供给我们使用的库函数,库函数在哪存放呢?库函数在如MSDN工具的文档中看:搜索printf()函数,在下面出现的LIBRIES就是库,各种.LIB文件就是库,在代码中使用了printf()函数,此时若要正常使用printf()函数,函数又在库中存放,编译器就像链接目标文件一样把链接库链接进去,最终在可执行程序中想要使用库函数时才能找到这个库函数,即链接的时候会把库函数中的库也链接进去,链接库中就提供了相关的函数,此时在可执行程序中就可以正常使用。
链接库不是用来识别头文件的,链接库是为了让我们使用的库函数有源头,知道库函数在哪存放。
相关知识补充:
C语言的库函数放在LIB目录里面,是存放在函数库中的函数,库函数是将函数封装入库,供用户使用的一种方式。方法是把一些常用到的函数编完放到一个文件里,供不同的人进行调用。调用的时候把它所在的文件名#include<>加到里面就可以了。一般是放到lib文件里的。
链接的时候链接的是.LIB文件(静态库),就可以使用函数。源代码都被变成静态库供人使用,静态库就是一种封装。
(静态库的后缀是.LIB,没有静态库时静态库中提供的函数是不可以使用的)
总结:
由源代码到产生结果的过程:
在工程中写出的各种.c文件经过编译器进行编译处理会生成各自对应的目标文件,多个目标文件再加上链接库一起经过链接器进行链接处理最终生成可执行程序,而可执行程序是二进制文件,里面放的是二进制指令即机器指令,这些机器指令是可以被硬件直接解读的,硬件环境加上操作系统的解读依赖运行环境就可以生成我们想要的结果。
编译整个过程展开又可以分成3个过程:预编译,编译、汇编,汇编之后产生的是目标文件。
源文件是怎么进行预编译的?怎进行编译的?怎么进行汇编的?最后是怎么进行链接的?
在Linux环境中演示,因为gcc编译器可以看到每一个步骤的效果,所以用gcc C语言编译器演示程序的编译和链接过程——每完成一次动作看结果。
在Linux环境中:
LS是列出当前目录底下的东西。
vim是编辑器,就像Windows中的记事本。如vim test.c是打开test.c文件。
1、怎么进行预编译呢?
用-E选项,编译test.c,在Linux中用gcc编译器就是:gcc -E test,或gcc test -E。
理解:-E这个选项去执行命令时,预处理就开始了,预处理产生的结果打印在屏幕上。若把预处理的结果放到文件中,则:gcc -E test.c -o test.i,-o的意思是输出一个文件,输出的文件夹是test.i文件,生成的test.i文件放的就是预处理之后的结果。
预处理的命令:gcc -E test.c -o test.i,只要执行这个命令,就会进行预编译。
预编译做了什么呢?
vim.test.i,打开test.i文件会发现相对test.c有很多代码,但是test.i文件中没有头文件的包含(没有#include
在Linux中找头文件:在usr路径下有include,在这里面有stdio.h的头文件。用vim工具打开头文件
stdio.h头文件,这样可以看stdio.h头文件的内容。
发现这两个文件的内容对比一样。
所以,预编译期间进行了头文件的展开。
预编译(预处理)做的事: