目录

从代码到可执行程序

1. 预处理   2.编译   3. 汇编    4. 链接

编译器gcc

gcc 命令集


说到编译器, 就不得不说一个程序从代码到可执行的过程了

从代码到可执行程序

我们的源代码到一个可执行程序可不是一步到位, 需要以下几个步骤:

预处理  -->  编译  -->  汇编  -->  链接

1. 预处理

对源代码进行宏替换, 文件展开, 去注释

我们在linux下看一下各个过程, 我们写一个简单的.c文件, 如下图 ,  注意, 我故意写错了一个, 在第一个printf后面没写分号

我们保存后, 可以用gcc将a.c中的代码预处理成一个新的a.i文件,命令为gcc -E a.c -o a.i , 这里不用关注这句命令具体的含义, 只需要知道是预处理就可以了, 下面我们会详细讲gcc/g++的用法 
我们打开生成的a.i文件, 如下图 :

可以看到, 注释被去掉了, 宏被原地展开了, 但a.i文件竟然变成了800多行, 前面a.c可只有12行, 这是因为, 预处理还会文件展开嘛, 预处理将头文件stdio.h展开了

2.编译

语法语义检测, 优化代码, 并生成汇编代码

前面a.c中写错, 就是为了方便编译测试, 我们可以用gcc -S a.i -o a.s  这个命令来用a.i 文件编译生成新的a.s文件, 我们来试一下, 能否编译通过, 结果如图 

可见, 编译时进行了语法语义的检测,  再将a.c改正确, 删掉原来的a.i, 将a.c重新预处理成a.i, 再编译, 结果生成了a.s文件, 编译通过, a.s内容如下图

可以看到, 编译将高级语言C转换成了汇编语言

3. 汇编

将汇编代码生成机器可识别代码

我们用gcc -c a.s -o a.o   这个命令用a.s汇编成新的a.o文件,  打开a.o文件如下图

可以看到, 全是些人看了头大, 却是计算机唯一能读懂的二进制, 这就是汇编代码生成的机器语言

4. 链接

什么是连接?

简单来说,就是把程序用到的所有源文件的目标文件合成一个可执行目标文件,是一个“多合一”的过程。

两种链接: 链接分为两种, 动态链接和静态链接, 下面简单介绍一下这两种

函数库: 函数库依照是否被链接到程序内部而分为静态库和动态库(共享库)

静态库扩展名: lib***.a   动态库扩展名: lib***.so

静态链接: 在链接时会将静态库直接整合到执行程序中,所以生成的可执行文件比较大,  编译成功的可执行文件可以独立执行, 不再需要外部的函数库

动态链接:在链接后生成的可执行文件中只有对动态库的引用(只链接了指针),当文件要使用函数库里的功能时,程序才会读取出函数库来使用。由于执行文件当中仅仅具有指向动态函数库的指针, 所以生成的可执行文件较小。生成的可执行程序不能独立执行,要执行的话, 动态库文件必须存在,且动态库所在的目录也不能被改变, 使用时,程序才会主动去某个路径下读取,所以动态库不能被随意删除。

静态链接库对程序的更新、部署、发布带来麻烦。如果静态库更新了, 使用它的应用程序都需要重新编译, 发布给用户(好比游戏, 对于玩家来说, 可能是一个很小的改动,却要导致整个程序重新下载,全量更新).   或当静态链接的程序多份运行或有多个源文件链接了静态库, 则会在内存中造成代码冗余

动态库在程序编译时并不会被链接到目标代码中,而是在执行文件中记录对动态库的引用,在程序运行时才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布带来的麻烦,用户只需要更新动态库即可,增量更新。

Linux下默认是动态链接

我们执行gcc a.o -o a  生成可执行程序a  , 执行程序如下图 :

编译器gcc

GCC(GNU Compiler Collection,GNU编译器套件),是由 GNU 开发的编程语言编译器。

GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理C语言。GCC 很快地扩展,变得可理 C++。后来又扩展能够支持更多编程语言,  所以改名GNU编译器套件(GNU Compiler Collection)。

gcc的安装命令   yum  install  gcc

g++的安装命令   yum install  gcc-c++

gcc 命令集

  • -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
  • -S 编译到汇编语言不进行汇编和链接
  • -c 编译到目标代码
  • -o 文件输出到 文件
  • -static 此选项对生成的文件采用静态链接
  • -g 生成调试信息。生成debug版本的可执行文件. GNU 调试器可利用该信息。
  • -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
  • -O0, -O1,  -O3, 编译器的优化选项的4个级别,依次优化级别越高, 也就编译越慢
  • -w 不生成任何警告信息。
  • -Wall 生成所有警告信息。
  • -L  添加优先搜索库文件路径

实例 :

假设源程序文件名为test.c

无选项编译链接   gcc test.c  ,将test.c预处理、汇编、编译并链接形成可执行文件。这里未指定输出文件,默认输出为a.out。

选项 -o  
gcc test.c -o   test将test.c预处理、汇编、编译并链接形成可执行文件test  , -o选项用来指定输出文件的文件名。

选项 -E
gcc -E test.c -o test.i 将test.c预处理输出test.i文件

选项 -S
gcc -S test.i将预处理输出文件test.i汇编成test.s文件

选项 -c
gcc -c test.s将汇编输出文件test.s编译输出test.o文件

无选项链接 gcc test.o -o test将编译输出文件test.o链接成最终可执行文件test

选项 -O
gcc -O1 test.c -o test使用编译优化级别1编译程序

多源文件的编译方法: 
如果有多个源文件,基本上有两种编译方法: 假设有两个源文件为test.c和testfun.c

多个文件一起编译
gcc testfun.c test.c -o test 将 testfun.c和test.c分别编译后链接成test可执行文件。

分别编译各个源文件, 之后对编译后输出的目标文件链接。
gcc -c testfun.c   //将testfun.c编译成testfun.o
gcc -c test.c        //将test.c编译成test.o
gcc -o testfun.o test.o -o test    //将testfun.o和test.o链接成可执行文件test

以上两种方法相比较,第一中方法编译时需要所有文件重新编译,而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译。
     

一起编译 如下:


 

分开编译再链接如下 : 

再将其testfun.c改为如下, 保存, 再编译后, 与test.o链接成可执行文件

 

 可以看到, 修改testfun.c文件后, 只需要把testfun.c重新编译一遍再与test.o链接就好了

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐