阅读 46

[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;  } 复制代码

image-20221023191516245

       然后./a.out就可以直接运行:

image-20221023191554387

       如果只想让文件进行预处理就停下来可以gcc –E hello.c –o hello.i,然后我们分屏打开这两个文件来比对一下:

image-20221023193033908

       顺便提一句,C头文件可以在这个路径下找到:/usr/include/。有了头文件才能支持代码自动补齐。

编译

       在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。

       选项“-S”:作用是只进行编译就停止,生成汇编代码。

       实例:gcc –S hello.i –o hello.s

       “.s”文件是编译后的包含汇编代码的文件。

  • 编译过程为 扫描程序-->语法分析-->语义分析-->源代码优化-->代码生成器-->目标代码优化。

  • 扫描程序进行词法分析,从左向右,从上往下扫描源程序字符,识别出各个单词,确定单词类型。

  • 语法分析是根据语法规则,将输入的语句构建出分析树parse tree或者语法树syntax tree。

  • 语义分析是根据上下文分析函数返回值类型是否对应这种语义检测,可以理解语法分析就是描述一个句子主宾谓是否符合规则,而语义用于检测句子的意思是否是正确的。

编译优化

  • 死代码删除是编译最优化技术,指的是移除根本执行不到的代码,或者对程序运行结果没有影响的代码,而并不是删除被注释的代码。

  • 内联函数,也叫编译时期展开函数, 指的是建议编译器将内联函数体插入并取代每一处调用函数的地方,从而节省函数调用带来的成本,使用方式类似于宏,但是与宏不同的是内联函数拥有参数类型的校验,以及调试信息,而宏只是文本替换而已。

  • for循环的循环控制变量,通常被cpu访问频繁,因此如果调度到寄存器中进行访问则不用每次从内存中取出数据,可以提高访问效率。

  • 强度削弱是指执行时间较短的指令等价地替代执行时间较长的指令,比如 num % 128num & 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 就是动态库。

image-20221024110339771

       顺便提一嘴,Windows下命名:动态库后缀.dll,静态库后缀.lib

       那我们怎么知道我们写的文件链接时用的是静态库还是动态库呢?在Linux下我们可以用file指令来查看一个文件的详细信息,比如:

image-20221024105842887

       实际上,gcc 在链接时默认是动态链接的,使用的是动态库。那使用的是哪一个库呢?使用ldd,比如ldd test可以查看可执行程序test依赖的动态库:

image-20221024110148971

       那如果我就是想要让gcc静态链接怎么办呢?系统一般默认具备动态库而不具备静态库,所以先安装一下 C静态库:yum install -y glibc-static,然后在gcc编译链接的时候后置一个-static即可(C++静态库的话是yum install -y libstdc++-static),比如:

image-20221024110912047

       我们顺便也可以看看动态链接和静态链接形成的文件大小比对情况,比如:

image-20221024111209206

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:

image-20221030114534427

       为什么不行呢?

       Linux gcc/g++得到的二进制程序,默认是release模式,所以要使用gdb调试,必须在源代码生成二进制程序的时候,加上 -g 选项加入调试信息而转变为debug模式,这样才能进行调试。

关于默认行为

gcc:

默认是动态链接

默认是release模式

vim:

默认是命令行模式

基本使用

       以下面代码为例进行简单讲解: image-20221030114335842

image-20221030115038734

image-20221030114407461

        ctrl + dquit(或q) :退出

       list(或l) 行号:显示源代码(一般不会从头开始),每次都接着上次的位置往下列,每次列10行。

image-20221030125321175

       list(或l) 函数名:列出某个函数的源代码。

       在执行完一个命令后想再次执行该命令可以直接按回车,因为gdb会保存上一次命令。

       break(或b) 行号:在某一行设置断点

       break(或b) 函数名:在某个函数开头设置断点

image-20221030130651927

       info break (或i b):查看断点信息。

image-20221030130744128

       Num对应的就是断点的序号。

       delete breakpoints(或d) n:删除序号为n的断点

image-20221030130835679

       delete breakpoints(或d):删除所有断点

       disable breakpoints:禁用断点

       enable breakpoints:启用断点

image-20221030184626079

image-20221030184616300

       断点若处于未启用状态,断点存在但会被忽略。

       run(或r):从程序开始处连续而非单步执行程序,遇到断点会停下来

       next(或n):一次执行一个过程(逐过程,不会进入函数调用) step(或s):一次执行单条语句(逐语句,会进入函数调用)

       continue(或c):从当前位置开始连续而非单步执行程序,遇到断点会停下来(一般用来从一个断点处运行到下一个断点处)

       finish:只执行到当前函数结束,然后停下来等待命令

image-20221030190418682

       print(或p):打印表达式的值,通过表达式可以修改变量的值或者调用函数 display 变量名:跟踪查看一个变量,每次执行停下来都显示它的值 undisplay 序号:取消对先前设置的那些变量的跟踪(根据序号) breaktrace(或bt):查看各级函数调用及参数 info(或i) locals:查看当前栈帧局部变量的值

image-20221030191824693

       until X(行号):跳至X行

image-20221030192036952


以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~


作者:桦秋静
链接:https://juejin.cn/post/7169153555897516062


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐