[Linux修炼]gcc和gdb的基本使用
前言
本文就来分享一波作者对Linux下gcc和gdb的学习心得与见解。
笔者水平有限,难免存在纰漏,欢迎指正交流。
Linux编译器-gcc/g++
这里讲的是gcc相关内容,g++的话也差不多。
背景知识
编译过程包括:
1.预处理(进行一系列文本操作) 2.编译(生成汇编代码) 3.汇编(生成机器可识别代码)
然后就是链接(生成可执行文件或库文件)
gcc如何执行
格式 :gcc [选项] [要编译的文件] [选项] [目标文件]
预处理
预处理功能主要包括宏替换,文件包含,条件编译,去注释等。
预处理指令是以#号开头的指令。
实例:gcc –E hello.c –o hello.i
选项“-E”:作用是让 gcc 在预处理结束后停止编译过程
选项“-o”:作用是将处理后的文件内容输出到目标文件中,“.i”文件为已经过预处理的C原始程序。
如下是hello.c文件的内容,如果直接gcc hello.c就会编译并链接直接生成默认的可执行文件a.out。
#include<stdio.h> #define M 100 int main() { printf("hello \n"); //测试注释 //printf("hello \n"); //printf("hello \n"); //printf("hello \n"); //printf("hello \n"); //printf("hello \n"); //测试条件编译 #ifdef SHOW printf("hello show!\n"); #else printf("hello default!\n"); #endif //测试宏 printf("宏:%d\n", M); return 0; } 复制代码 然后./a.out就可以直接运行:
如果只想让文件进行预处理就停下来可以gcc –E hello.c –o hello.i,然后我们分屏打开这两个文件来比对一下:
顺便提一句,C头文件可以在这个路径下找到:/usr/include/。有了头文件才能支持代码自动补齐。
编译
在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。
选项“-S”:作用是只进行编译就停止,生成汇编代码。
实例:gcc –S hello.i –o hello.s
“.s”文件是编译后的包含汇编代码的文件。
编译过程为 扫描程序-->语法分析-->语义分析-->源代码优化-->代码生成器-->目标代码优化。
扫描程序进行词法分析,从左向右,从上往下扫描源程序字符,识别出各个单词,确定单词类型。
语法分析是根据语法规则,将输入的语句构建出分析树parse tree或者语法树syntax tree。
语义分析是根据上下文分析函数返回值类型是否对应这种语义检测,可以理解语法分析就是描述一个句子主宾谓是否符合规则,而语义用于检测句子的意思是否是正确的。
编译优化
死代码删除是编译最优化技术,指的是移除根本执行不到的代码,或者对程序运行结果没有影响的代码,而并不是删除被注释的代码。
内联函数,也叫编译时期展开函数, 指的是建议编译器将内联函数体插入并取代每一处调用函数的地方,从而节省函数调用带来的成本,使用方式类似于宏,但是与宏不同的是内联函数拥有参数类型的校验,以及调试信息,而宏只是文本替换而已。
for循环的循环控制变量,通常被cpu访问频繁,因此如果调度到寄存器中进行访问则不用每次从内存中取出数据,可以提高访问效率。
强度削弱是指执行时间较短的指令等价地替代执行时间较长的指令,比如
num % 128与num & 127相较,则明显&127更加轻量。
汇编
汇编阶段是把编译阶段生成的“.s”文件转成目标文件,但还不是可执行文件。 选项“-c”:作用是只进行汇编就停止,生成二进制指令。
实例:gcc –c hello.s –o hello.o
“.o”为二进制可重定向目标文件。
链接
在成功编译之后,就进入了链接阶段,该阶段内把目标文件和标准库联系起来,这样你调用的库函数才能找到定义并执行,链接完后就形成了可执行文件。
实例: gcc hello.o –o hello
讲完这些个过程后,这里提供一个助记小方法:注意到键盘左上角的[Esc]键没?这不正好对应-E、-S和-c么?
初识动静态链接
首先要清楚,我们自己写的代码和库是两码事。诸如C标准库等是别人已经给我们准备好的,让我们直接使用。我们所有使用库函数的代码都只是函数调用,并没有对应的函数实现,只有当链接的时候库中的实现才和我们写的代码关联起来。
在这里涉及到一个重要的概念:函数库。比如在我们的C程序中,并没有定义printf的函数实现,且在预编译中包含的stdio.h中也只有该函数的声明,而 没有定义函数的实现,那么,是在哪里实现printf函数的呢? 答案是:(Linux下)系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索路径/usr/lib下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数printf了,而这也就是链接的作用。那我们写的代码和库函数如何关联呢?
函数库一般分为静态库和动态库两种,分别适用于静态链接和动态链接。
静态链接是指链接时,把要用到的代码从静态库中拷贝一份到目标文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了,所以不受库升级或者被删除的影响,但是形成的可执行程序体积太大,对于磁盘、内存及网络资源占用大,会使得各个可执行程序文件中存在冗余代码。(Linux下)静态库后缀名一般为.a。
动态链接与之相反,在链接时并没有把动态库中所需代码拷贝到目标文件中,而是与动态库建立一些联系,使得在程序执行时能动态跳转到库中实现某些函数再跳回,这样可以节省系统的开销,形成的可执行程序小,这不仅仅使得在磁盘上占用空间小,加载到内存能节省资源速度快,而且便于网络中传输。(Linux下)动态库一般后缀名为.so,如前面所述的 libc.so.6 就是动态库。
顺便提一嘴,Windows下命名:动态库后缀.dll,静态库后缀.lib。
那我们怎么知道我们写的文件链接时用的是静态库还是动态库呢?在Linux下我们可以用file指令来查看一个文件的详细信息,比如:
实际上,gcc 在链接时默认是动态链接的,使用的是动态库。那使用的是哪一个库呢?使用ldd,比如ldd test可以查看可执行程序test依赖的动态库:
那如果我就是想要让gcc静态链接怎么办呢?系统一般默认具备动态库而不具备静态库,所以先安装一下 C静态库:yum install -y glibc-static,然后在gcc编译链接的时候后置一个-static即可(C++静态库的话是yum install -y libstdc++-static),比如:
我们顺便也可以看看动态链接和静态链接形成的文件大小比对情况,比如:
gcc选项
-E 只进行预处理
-S 编译成汇编语言而不进行汇编和链接
-c 汇编成二进制指令但不链接
-o 文件处理后的内容输出到目标文件
-static 此选项对生成的文件采用静态链接
g 生成调试信息,GNU 调试器可利用该信息
-shared 此选项将尽量使用动态库,生成文件比较小,但是需要系统有动态库
编译器的优化选项的4个级别
-O0 -O1 -O2 -O3 ,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-w 不生成任何警告信息
-Wall 生成所有警告信息
Linux调试器- gdb
背景
程序的发布方式有两种,debug模式和release模式
程序员需要调试(对应debug模式,包含了调试信息)
用户用不到——不会用也不关心(对应release模式,去除了调试信息)
我们在Linux下试一试gdb:
为什么不行呢?
Linux gcc/g++得到的二进制程序,默认是release模式,所以要使用gdb调试,必须在源代码生成二进制程序的时候,加上 -g 选项加入调试信息而转变为debug模式,这样才能进行调试。
关于默认行为
gcc:
默认是动态链接
默认是release模式
vim:
默认是命令行模式
基本使用
以下面代码为例进行简单讲解:
ctrl + d 或 quit(或q) :退出
list(或l) 行号:显示源代码(一般不会从头开始),每次都接着上次的位置往下列,每次列10行。
list(或l) 函数名:列出某个函数的源代码。
在执行完一个命令后想再次执行该命令可以直接按回车,因为gdb会保存上一次命令。
break(或b) 行号:在某一行设置断点
break(或b) 函数名:在某个函数开头设置断点
info break (或i b):查看断点信息。
Num对应的就是断点的序号。
delete breakpoints(或d) n:删除序号为n的断点
delete breakpoints(或d):删除所有断点
disable breakpoints:禁用断点
enable breakpoints:启用断点
断点若处于未启用状态,断点存在但会被忽略。
run(或r):从程序开始处连续而非单步执行程序,遇到断点会停下来
next(或n):一次执行一个过程(逐过程,不会进入函数调用) step(或s):一次执行单条语句(逐语句,会进入函数调用)
continue(或c):从当前位置开始连续而非单步执行程序,遇到断点会停下来(一般用来从一个断点处运行到下一个断点处)
finish:只执行到当前函数结束,然后停下来等待命令
print(或p):打印表达式的值,通过表达式可以修改变量的值或者调用函数 display 变量名:跟踪查看一个变量,每次执行停下来都显示它的值 undisplay 序号:取消对先前设置的那些变量的跟踪(根据序号) breaktrace(或bt):查看各级函数调用及参数 info(或i) locals:查看当前栈帧局部变量的值
until X(行号):跳至X行
以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~
作者:桦秋静
链接:https://juejin.cn/post/7169153555897516062