早期的时候计算机是通过打孔纸带进行编程的,如下图就是打孔纸带的图片
然后有了我们的汇编语言
然后再有我们的c语言/c++
计算机只认识二进制,不认识汇编,也不认识c语言。汇编这种形式都是字符的形式,已经接近人类语言了,所以就有了编译器的出现。最开始的时候用二进制语言实现写一个二进制编译器用来将汇编语言翻译成二进制,后来发现汇编语言用起来也不爽,就有了c语言/c++的诞生,那么我们既然能够用二进制写一个二进制编译器来翻译汇编语言,同样的,我们也可以通过汇编语言写一个编译器用来翻译c语言/c++。那么我们能不能直接通过用二进制写一个翻译c语言/c++的编译器呢?答案是可以的,但是没必要,那样的话开发成本会很大。 永远记住一句话:编译器也是软件!
通过这里我们就可以更好的理解c语言程序编译过程为什么会有这些阶段?
GCC(GNU C Compiler)原名GNU C语言编译器,是由GNU开发的编程语言译器,只能处理C语言。但其很快扩展,变得可处理C++,后来又扩展为能够支持更多编程语言,如Fortran、Pascal、Objective -C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)。GCC 编译器是 Linux 系统下最常用的 C/C++ 编译器,大部分 Linux 发行版中都会默认安装。GCC 编译器通常以gcc命令的形式在终端(Shell)中使用。
.c C 语言文件
.i 预处理后的 C 语言文件
.C、.cc、.cp、.cpp、.c++、.cxx C++语言文件
.ii 预处理后的 C++ 语言文件
.S 汇编文件(用户自己新建的汇编文件)
.s 预处理后的汇编文件
.o 编译后的目标文件
.a 目标文件的静态链接库(链接时使用)
.so 目标文件的动态链接库(链接、运行时使用)
在了解gcc的使用之前,我们可以先理解一下c语言源文件的编译过程,其中分为四个阶段,分别是:
预处理:1.头文件展开 2.去注释 3.条件编译 4.宏替换
编译 : 把c 编译成汇编语言
汇编: 汇编----> 可重定位的二进制文件
链接: 生成可执行文件或库文件
这是从c语言源文件的角度去看待编译过程的,现在通过一个案例从gcc编译器的角度去分析:
先创建一个test.c源文件,然后用vim编写如下代码:
保存后,执行 命令 gcc -E test.c
发现屏幕上会打印出一大堆c语言代码,其实就是在进行预处理(1.头文件展开 2.去注释 3.条件编译 4.宏替换)进行这主要的四个部分
前面这些显示出来的都是一下头文件的路径,把头文件拷贝到这个文件然后展开,说白了就是把文件做合并,看到这上几百行代码打印在屏幕上看着就不太方便,也不好操作,于是我们可以将这些内容重定向到一个文件里面
然后用vim打开查看
可以看到原本打印在屏幕上的代码就写进test.i的文件里面了。
这里可以看到之前我们代码的注释被去掉了
而且那个M也被123给替换掉了,所以这里体现出了去注释和宏替换操作。
条件编译,我们修改test.c里面的代码,看如下代码:
所以这个案例可以说明,通过条件编译可以实现对代码的动态裁剪。
我们生活中的例子,比如说软件,有很多软件都是同一种软件,但是功能却又差异,有的是收费的,有的是免费的。背后源代码需要维护几份呢? 答案是1份。
下面来看gcc编译过程的第二个阶段:编译
我们对前面进行test.c预处理生成的临时文件test.i进行编译操作需要执行命令
gcc -S test.i -o test.s
然后在vim里面同时打开三个文件test.c ,test.i , test.s
发现test.s里面的是汇编代码
那么这个gcc -S test.i -o test.s这行命令里面的-S是什么意思呢?
-S表示从现在开始进行程序的翻译,等编译工作做完就停下来
下面来看gcc编译过程的第三个阶段:汇编
前面提到了,编译工作生成了汇编代码之后我们需要gcc通过汇编操作把汇编代码转换成可重定位的二进制文件。
我们对前面进行test.i编译生成的临时文件test.s进行编译操作需要执行命令
gcc -c test.s -o test.o
之后进入vim 查看我们的test.o文件
刚开始打开呢我们都看不懂这个二进制文件,里面全是一些字符,下面我们借助一个专门用来看二进制文件的工具执行命令
od test.o
这是以八进制显示的二进制重定位文件
gcc -c test.s -o test.o 这里的-c选项呢代表昨晚汇编工作就停下来
这个test.o还不能执行
如果想要我们的这个代码执行起来还需要进行最后一个阶段:链接
*. o + 系统库 = 可执行程序
这个阶段.o的目标文件是要去链接系统库的
通过执行命令
gcc test.o
会生成可执行文件a.out
然后./a.out就可以执行这个程序了
当然你也可以自己命名
执行命令
gcc test.o -o mybin
最后生成的可执行程序就是mybin
同样的也可以执行了。
那么这里这么多选项这么多文件的后缀是不是觉得特别难记住啊,特别容易搞混啊,那么我可以教大家一个方法:
以后我们直接想要编译运行直接执行命令
1.gcc test.c -o mybin
2. gcc 命令的选项 :-ESc
-E:执行完预处理工作停下来
-S: 执行完编译工作停下来
-c:执行完汇编工作停下来
ESc就是我们键盘最左上角的一个键位,我们只需要记住E S 是大写, c是小写
文件后缀:test.i + test.s+ test.o = .iso
g++相当于是对gcc的封装
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而
没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到
系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函
数“printf”了,而这也就是链接的作用
这里涉及到一个重要概念:
函数库一般分为静态库和动态库两种。
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也
就不再需要库文件了。其后缀名一般为“.a”
动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时
链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态
库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,如下所示。 gcc
hello.o –o hello
gcc默认生成的二进制程序,是动态链接的,这点可以通过 file 命令验证。