要更深入了解C++, 必须要知道一个程序从开始到结束都干了些什么, 怎么干的。 这篇博客从C++编译到运行过程,解析下各个过程是怎样进行的。
编译过程
C++源程序-> 预编译处理(.c)-> 编译、优化程序(.asm、.s)-> 汇编程序(.obj、.o、.a)-> 链接程序(.exe等可执行文件)
- 预编译:处理源代码中的以”#”开始的预编译指令,如”#include”、”#define”等。
- 编译:对预处理完的文件按照词法分析、语法分析、语义分析以及优化,进而产出相应的汇编代码文件。这是程序构建的核心部分,也是最复杂的部分之一。
- 汇编:将汇编代码编译成机器语言,一个汇编语句一般对应一条机器语句。
- 链接:将多个目标文件连接起来形成可执行文件。
其中编译可以分为以下几个步骤(编译原理里面的知识 /捂脸 )
- 词法分析:将源代码程序输入扫描器,将源代码字符序列分割成一系列记号(Token)。
- 语法分析:对产生的记号使用上下文无关语法进行语法分析,产生语法树。语法分析:对产生的记号使用上下文无关语法进行语法分析,产生语法树。
- 语义分析:进行静态语义分析,通常包括声明和类型的匹配,类型的转换。语义分析:进行静态语义分析,通常包括声明和类型的匹配,类型的转换。
- 中间语言生成:使用源代码优化器将语法树转换成中间代码并进行源码级的优化。中间语言生成:使用源代码优化器将语法树转换成中间代码并进行源码级的优化。
- 目标代码生成:使用代码生成器将中间代码转成依赖于具体机器的目标机器代码。目标代码生成:使用代码生成器将中间代码转成依赖于具体机器的目标机器代码。
- 目标代码优化:使用目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用位移替代乘法、删除多余指令等。目标代码优化:使用目标代码优化器对目标代码进行优化,比如选择合适的寻址方式、使用位移替代乘法、删除多余指令等。
编译器编译源代码生成的文件称为目标文件,目标文件是按照可执行文件的格式存储的,linux下的目标文件和可执行文件可以看作是一种类型的文件,统称为ELF文件,一般有以下几种:
- 可重定位文件:如.o文件,包含代码和数据,可以被链接成可执行文件或共享目标文件,静态链接库属于这一类。
- 可执行文件:如/bin/bash文件,包含了可以直接执行的程序,一般没有扩展名。
- 共享目标文件:如.so文件,包含代码和数据,可以跟其他可重定位文件和共享目标文件链接产生新的目标文件,也可以跟可执行文件结合作为进程映像的一部分。
链接过程
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
(1)静态链接
在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
(2)动态链接
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。
总结:
C++语言编译的整个过程是非常复杂的,里面涉及到的编译器知识、硬件知识、工具链知识都是非常多的,深入了解整个编译过程对工程师理解应用程序的编写是有很大帮助的,希望大家可以多了解一些,在遇到问题时多思考、多实践。
一般情况下,我们只需要知道分成编译和连接两个阶段,编译阶段将源程序(*.c)转换成为目标代码(一般是obj文件),链接阶段是把源程序转换成的目标代码(obj文件)与你程序里面调用的库函数对应的代码连接起来形成对应的可执行文件(exe文件)就可以了,其他的都需要在实践中多多体会才能有更深的理解。
make & makefile
Makefile 是和 make 命令一起配合使用的.
很多大型项目的编译都是通过 Makefile 来组织的, 如果没有 Makefile, 那很多项目中各种库和代码之间的依赖关系不知会多复杂.
Makefile的组织流程的能力如此之强, 不仅可以用来编译项目, 还可以用来组织我们平时的一些日常操作. 这个需要大家发挥自己的想象力。
详情参见博客:https://www.cnblogs.com/wang_yb/p/3990952.html
cmake
你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
cmake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等 [1]。
CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。
详情参看博客:http://www.hahack.com/codes/cmake/
gdb调试命令
gcc -g main.c //在目标文件加入源代码的信息
gdb a.out
(gdb) start //开始调试
(gdb) n //一条一条执行
(gdb) step/s //执行下一条,如果函数进入函数
(gdb) backtrace/bt //查看函数调用栈帧
(gdb) info/i locals //查看当前栈帧局部变量
(gdb) frame/f //选择栈帧,再查看局部变量
(gdb) print/p //打印变量的值
(gdb) finish //运行到当前函数返回
(gdb) set var sum=0 //修改变量值
(gdb) list/l 行号或函数名 //列出源码
(gdb) display/undisplay sum //每次停下显示变量的值/取消跟踪
(gdb) break/b 行号或函数名 //设置断点
(gdb) continue/c //连续运行
(gdb) info/i breakpoints //查看已经设置的断点
(gdb) delete breakpoints 2 //删除某个断点
(gdb) disable/enable breakpoints 3 //禁用/启用某个断点
(gdb) break 9 if sum != 0 //满足条件才激活断点
(gdb) run/r //重新从程序开头连续执行
(gdb) watch input[4] //设置观察点
(gdb) info/i watchpoints //查看设置的观察点
(gdb) x/7b input //打印存储器内容,b--每个字节一组,7--7组
(gdb) disassemble //反汇编当前函数或指定函数
(gdb) si // 一条指令一条指令调试 而 s 是一行一行代码
(gdb) info registers // 显示所有寄存器的当前值
(gdb) x/20 $esp //查看内存中开始的20个数
多种内存查看命令:
格式: x /nfu
x 是 examine 的缩写
n表示要显示的内存单元的个数
f表示显示方式, 可取如下值
(1)x 按十六进制格式显示变量。
(2)d 按十进制格式显示变量。
(3)u 按十进制格式显示无符号整型。
(4)o 按八进制格式显示变量。
(5)t 按二进制格式显示变量。
(6)a 按十六进制格式显示变量。
(7) i 指令地址格式
(8) c按字符格式显示变量。
(9) f 按浮点数格式显示变量。
u表示一个地址单元的长度
(1).b表示单字节,
(2).h表示双字节,
(3).w表示四字节,
(4).g表示八字节
比如:x/3xh buf
- x/3xh buf 表示从内存地址buf读取内容,3表示三个单位,x表示按十六进制显示,h表示以双字节为一个单位。
多线程查看命令:
info threads //查看线程
thread thread_no //切换到线程号
thread apply all command //所有线程都执行命令打印栈桢
比如:thread apply all bt //所有线程都打印栈桢
更多请查看博客:https://blog.csdn.net/earbao/article/details/53958812