• C/C++源程序到可执行程序exe的全过程(及汇编和反汇编的区别)


    一个现代编译器的主要工作流程如下:

    源程序(source code)→预处理器(preprocessor)→编译器(compiler)→汇编程序(assembler)→目标程序(object code)→连接器(链接器,Linker)→可执行程序(executables)。

    简言之,源文件生成可执行文件的过程总共是经历了预编译/预处理,编译,汇编,链接这四个过程。

    如下图所示:

    下面用源文件test.c进行解释,test.c中代码为:

    #include <stdio.h>
    int main()
    {
           printf("hello world!
    ");
           return 0;
    }

    如输入命令:gcc test.c 

    则会完成上述四个过程,直接默认生成可执行文件a.out

    如下图所示:

    gcc参数如下:

    下面分别对上述四个过程进行说明。

    1.预编译/预处理——生成test.i

    命令:gcc -E test.c > test.i  或  gcc -E test.c -o test.i

    注:只有预编译/预处理可以>重定向,因为 gcc -E test.c的结果会在终端显示而不生成文件。

    其实预编译就是我们所说的预处理。 

    预编译/预处理主要作用:

    处理关于 “#” 的指令

    【1】删除#define,展开所有宏定义。例#define portnumber 8080

    【2】处理条件预编译 #if, #ifdef, #if, #elif,#endif

    【3】处理“#include”预编译指令,将包含的“.h”文件插入对应位置。这可是递归进行的,文件内可能包含其他“.h”文件。

    【4】删除所有注释。/**/,//。

    【5】添加行号和文件标识符。用于显示调试信息:错误或警告的位置。

    【6】保留#pragma编译器指令。(1)设定编译器状态,(2)指示编译器完成一些特定的动作。

    在四个过程中,第一个进行的是预编译的过程。 

    接下来,我们一步步详细讨论下其中发生的:

    第一步发生的是预编译,在这我们使用-E指令,使用这个指令会使程序只进行到预编译指令。把预编译的结果放到test.i文件中。在预编译的过程中,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏的替换,添加行号,保留所有的编译器指令。

    下图就是预编译后得到的结果 。

    所以当进行预编译以后的文件中将不再存在宏,所有的宏都已经被替代。当我们想要判断宏是否正确或者头文件包含是否正确的时候,我们也可以通过预编译来查看。

    2.编译——生成汇编代码文件test.s

    命令:gcc -S test.c -o test.s 或 gcc -S test.i -o test.s

    注:gcc -S test.c 或 gcc -S test.i 会默认生成test.s文件。

    在预处理结束后,我们所要进行的就是编译。编译过程所进行的是对预处理后的文件进行语法分析,词法分析,语义分析,符号汇总,然后生成汇编代码文件test.s

    test.s打开以后的结果是:

    从结果我们可以知道,得到的是汇编代码。

    3.汇编——生成二进制目标文件test.o

    命令:gcc -c test.c -o test.o 或 gcc -c test.s -o test.o  gcc -c test.i -o test.o

    注1:gcc -c test.c 或 gcc -c test.s 或 gcc -c test.i 会默认生成test.o文件。

    注2:gcc -c x1.c x2.c  结果将生成:两个目标文件x1.o  x2.o

    这里的汇编所说的是一个过程,将汇编代码转成二进制文件,二进制文件就可以让机器来读取。每一条汇编语句都会产生一句机器语言。相当于windows下的.obj文件。这里生成的目标文件里面就是二进制文件。另外,在这需要注意的是,会形成符号表,给这些符号会分配虚拟地址。

    4.链接——生成默认a.out

    命令:gcc test1.c test2.c -o test 或 gcc test1.o test2.o -o test

    链接,其实就是将二进制文件链接成为一个可执行的指令。 

    链接所完成的任务是合并段表,然后把符号表合并并且对符号表进行重定位。

    所谓合并段表,源代码编译生成的a.out会包含很多段,数据段文本段bss段等等,这些段是合并出来的,在编译过程中划分出来的,不同的数据会对应到不同的段中,在.o文件中其实已经发生了分段。

    符号表合并和重定位说的是最后只生成了一个符号表,这个符号表是由前面汇编形成的多个符号表进行合并。在这里不在同一个符号表的符号,要对他们进行重定位。

    补充1:Microsoft Visual Studio中的源代码到可执行程序exe如下图所示:

    图中“编译器”(相当于VS中的编译Ctrl+F7),其实包含了预处理,编译,汇编三个过程。

    Microsoft Visual Studio中的编译(Ctrl+F7)相当于前述预处理,编译,汇编三个过程,将生成obj目标文件。但不会生成exe文件。

    我想这也是大部分人将“编译”理解为生成obj文件的原因。其实汇编才是生成obj文件,编译是生成汇编代码文件。

    补充2:汇编和反汇编的区别

    汇编是将汇编语言翻译成机器语言的过程。 也就是test.s文件经过汇编器变成二进制目标文件test.o文件的过程。

    反汇编指的是由已生成的机器语言(二进制语言)转化为汇编语言的过程,也就是汇编的逆向过程。

    在Linux下利用反汇编器对.o文件进行反汇编。 如下图所示。
    命令: objdump -d test.o  

    可看出和test.s中内容几乎一样!

  • 相关阅读:
    114.完全背包【恰好装满完全背包
    整数划分类型题目
    三目运算符
    关于 xk 的位数。
    多重背包二进制原理拆分问题
    2016.4.2 讲课笔记(动态规划)
    二叉树(2)二叉树创建的3种方法,二叉树的递归遍历,二叉树的销毁
    二叉树(1)已知某2种排序方式,创建这个二叉树并按第3种方式排序
    C++知识点总结(二)
    5款优秀的开源克隆软件
  • 原文地址:https://www.cnblogs.com/a3192048/p/12241248.html
Copyright © 2020-2023  润新知