大家好我是沐曦希💕
文章目录
1.背景知识
1.1 预处理
预处理功能主要包括宏定义,文件包含,条件编译,去注释等。
预处理指令是以#号开头的代码行。
实例: gcc –E hello.c –o hello.i
选项“-E”,该选项的作用是让 gcc 在预处理结束后停止编译过程。
选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序。
预处理时候编译器会把头文件展开,去掉注释,宏替换,条件编译等
选项 -E 是进行程序的翻译,预处理做完就停止编译过程。
选项“-o”是指目标文件,“.i”文件为已经过预处理的C原始程序
头文件展开,去注释,宏替换,条件编译
gcc -E test.c -o test.i
-E:从现在开始,进行程序的翻译,当你将预处理做完,就停下来
-o:指明形成的临时文件名称(.i)
此时生成test.i,我们用vim进入test.c,在利用底行模式vs test.i:
可以看到预处理把头文件展开了,去注释,宏替换,条件编译。(此时临时文件还是c语言)
可以向编译器传参来保证宏在命令行中定义
1.2 编译(生成汇编)
在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。
用户可以使用“-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
实例: gcc –S hello.i –o hello.s
把C转换成汇编语言:
S:从现在开始,进行程序的翻译,做完编译工作,变成汇编之后,就停下来
用vim打开test.s我们就可以看到汇编代码了:
1.3 汇编(生成机器可识别代码)
汇编阶段是把编译阶段生成的“.s”文件转成目标文件
读者在此可使用选项“-c”就可看到汇编代码已转化为“.o”的二进制目标代码了
实例: gcc –c hello.s –o hello.o
注意的是:此时生成的临时文件不是可以执行大的,是二进制目标文件,不能被执行,因为少了链接。
-c 从现在开始,进程程序的翻译,做完汇编工作变成可重定向目标二进制,就停下来
用vim打开test.o,但是我们根本看不懂:
再输入指令od test.o(以二进制形式打开该文件):
1.4 链接接(生成可执行文件或库文件)
链接的过程就是把写的代码和C标准库中的代码合起来
在成功编译之后,就进入了链接阶段。
实例: gcc hello.o –o hello
-o 链接的过程,形成可执行程序,可执行的二进制程序(库+你的代码)
1.5 习题
习题一
1.在编译过程中,产生parse tree的过程是哪个阶段?
A.语法分析
B.语义分析阶段
C.词法分析
D.目标代码生成阶段
答案:A
- 编译过程为 扫描程序–>语法分析–>语义分析–>源代码优化–>代码生成器–>目标代码优化;
- 扫描程序进行词法分析,从左向右,从上往下扫描源程序字符,识别出各个单词,确定单词类型
- 语法分析是根据语法规则,将输入的语句构建出分析树,或者语法树,也就是我们答案中提到的分析树parse tree或者语法树syntax tree
- 语义分析是根据上下文分析函数返回值类型是否对应这种语义检测,可以理解语法分析就是描述一个句子主宾谓是否符合规则,而语义用于检测句子的意思是否是正确的
- 目标代码生成指的是,把中间代码变换成为特定机器上的低级语言代码。
- 根据以上各个阶段的理解,可以分析出正确选项为A选项。
习题二
2.程序的完整编译过程分为是:预处理,编译,汇编等,
如下关于编译阶段的编译优化的说法中不正确的是
A.死代码删除指的是编译过程直接抛弃掉被注释的代码
B.函数内联可以避免函数调用中压栈和退栈的开销
C.for循环的循环控制变量通常很适合调度到寄存器访问
D.强度削弱是指执行时间较短的指令等价的替代执行时间较长的指令
答案:A
- 死代码删除是编译最优化技术,指的是移除根本执行不到的代码,或者对程序运行结果没有影响的代码,而并不是删除被注释的代码,因此A选项错误
- 内联函数,也叫编译时期展开函数, 指的是建议编译器将内联函数体插入并取代每一处调用函数的地方,从而节省函数调用带来的成本,使用方式类似于宏,但是与宏不同的是内联函数拥有参数类型的校验,以及调试信息,而宏只是文本替换而已。因此B选项正确
- for循环的循环控制变量,通常被cpu访问频繁,因此如果调度到寄存器中进行访问则不用每次从内存中取出数据,可以提高访问效率,因此C选项正确
- 强度削弱是指执行时间较短的指令等价的替代执行时间较长的指令,比如 num % 128 与 num & 127 相较,则明显&127更加轻量, 故D也是正确的
2.函数库
注意:我们自己写的代码和库是两码事。
链接的本质就是我们调用库函数的时候和标准库,如何关联的问题。
C标准库是别人已经给我们准备好的,直接使用,我们所有使用库中函数的代码(比如printf)其中我们自己只写了该函数的调用,没有对应的实现,只有当链接的时候,对应的实现,才和我们的代码关联起来.
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用
- 验证是动态链接还是静态链接
第一步先生成可执行文件
gcc test.o -o mytest
第二步查看可执行文件什么方式链接的
file mytest
dynamically linked 是动态链接
第三步查看可执行文件的动态库:
其中libc.so.6就是动态库。
函数库一般分为静态库和动态库两种
2.1 动态库
动态库在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为“.so”,如前面所述的 libc.so.6 就是动态库。gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件。
形成的可执行程序小(节省资源,内存,磁盘,网络)
2.2 静态库
静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a
不受库升级或者被删除的影响,线程的可执行程序体积太大,网络、磁盘、内存占用过大
静态库是将C标准库拷贝一份放在你程序里面,链接不是产生关联,而是在我们程序内部要用的方法,给我的程序拷贝一份,就完成了静态链接。
优势:不受库升级或者库被删除的影响。
缺点:形成的可执行程序体积过大–网络,磁盘,内存
去掉前缀lib去掉后缀.so剩下的就是库名称
对于上面的libc.so.6去掉后就剩下C,所以这个库是C标准库(动态库)
这个库的位置:
ll /lib64/libc.so.6
Linux下默认形成可执行程序,默认使用的是动态库 /lib64/libc-2.17.so
- 生成静态链接 生成可执行程序的命令后面要加上-static
gcc test.c -o mytest.s -static
查看链接方式:
statically linked 是静态链接,其中ldd命令只能查看动态链接的可执行程序。
我们不能删掉系统中的C动态库,因为Linux的命令都是动态链接的,不要删除C动态库!!!用这个库的程序非常多,但是库只有一份,所有用C语言写的程序,不会出现重复的库代码。
那么动态库相当于一个共享库,以后我们下载一个c写的程序,不用下C标准库。
静态链接拷贝的不是.so内部的代码,拷贝的是系统里必须存在.a结尾的静态库。/lib64/lib.a静态库。
动态链接只能找动态库,静态链接只能找静态库(一般而言,系统会自动携带动态库,因为系统运行需要动态库。静态库如果不存在,需要自己安装!)
- 手动安装静态库
查看libc.a是否已经安装:
sudo find / -name 'libc.a'
安装:
sudo yum install -y glibc-static
3.g++的基本使用
- 安装g++
sudo yum install -y gcc-c++
- 安装C++静态库
sudo yum install -y libstdc++-static
当然了C++也有相应的静态库和动态库
4.gcc选项
-E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
-S 编译到汇编语言不进行汇编和链接
-c 编译到目标代码
-o 文件输出到 文件
-static 此选项对生成的文件采用静态链接
-g 生成调试信息。GNU 调试器可利用该信息。
-shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
-O0
-O1
-O2
-O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-w 不生成任何警告信息。
-Wall 生成所有警告信息。
4.写在最后
系统本身为了支持我们编程,个我们提供了标准库的.h(告诉我们怎么用)
标准的动静态库.so/.a(告诉我们方法实现)
我们的程序通过使用包含头文件,然后链接别人的库来实现将我的代码+库的代码形成可执行程序!!!
我的代码+库的代码==可执行程序
另外,windows下的原理是一样的默认形成可执行动态链接,动态:.dll,静态:.lib
版权归原作者 沐曦希 所有, 如有侵权,请联系我们删除。