一般生成C/C++可执行程序需要经过以下四个步骤:1.预处理(头文件展开、去注释、宏替换、条件编译)。2.编译(C代码翻译成汇编语言)。3.汇编(汇编代码转为二进制目标代码)。4.链接(将目标文件和系统库进行链接形成可执行程序)。gcc/g++就是用来实现这四个步骤的,gdb则是一个调试器,用来debug。
Linux默认生成的可执行程序是动态链接且以release方式发布的!
文章目录
- 编译器gcc/g++
- 预处理
- 编译
- 汇编
- 链接
- 动态库和静态库
- 调试器gdb
- 调试
- 显示
- 断点
- 退出gdb
- 补充内容
编译器gcc/g++
gcc用来编译C语言程序,g++用来编译C++程序。它俩的选项基本一致。
语法:
gcc/g++ [选项] 要编译的文件 [选项] [目标文件]
常用选项:
- -E 只进行预处理,不生成对应的文件,需要把预处理后的信息重定向到一个输出文件里面(否则将把预处理后的结果打印到屏幕上)。
- -S 编译到汇编语言,不进行汇编和链接,即只进行预处理和编译。
- -c 编译到二进制目标代码
- -o 将处理结果输出到指定文件,该选项后需紧跟输出文件名。
- -static 此选项对生成的文件采用静态链接。
- -g 生成调试信息用于debug(若不携带该选项则默认生成release版本)。
- -shared 此选项将尽量使用动态库,生成文件较小。
- -w 不生成任何警告信息。
- Wall 生成所有警告信息。
- -O0/-O1/-O2/-O3 编译器优化选项的四个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高。
预处理
通过-E
选项可以将文件进行预处理,然后通过-o
选项将预处理后的信息输出到指定的文件(test.i)中。
预处理阶段的工作主要包括头文件展开、去注释、宏替换、条件编译等。
以test.c文件为例:
gcc -E test.c -o test.i
:
编译
编译阶段做的工作,首先检查代码的规范性、是否有语法错误(比如变量名错误等,如果是函数名写错,则是在链接阶段检测出来)等,如果有错误会停止并报错,在检查无误后,将代码翻译成汇编语言。
与预处理不同的是,即使不加-o
选项,也会生成对应的.s
文件,但为了规范还是应该加上,以test.i文件编译成test.s为例:
gcc -S test.i -o test.s
汇编
汇编阶段的工作是将编译产生的汇编文件转化为.o
二进制目标文件。.o
文件就是VS编译器下的.obj
文件。
gcc -c test.s -o test.o
由于是二进制文件,所以vim文本编辑器打开以后是乱码:
od 文件名
可以查看二进制文件:
链接
链接的主要任务就是将生成的各个.o
文件以及库文件进行链接,生成可执行文件。
gcc test.o -o test
链接后生成的也是二进制文件:
gcc/g++不带-E、-S、-c选项时,就默认生成预处理、编译、汇编、链接全过程后的文件。
如果不用-o选项指定生成文件的文件名,则默认生成的可执行文件名为a.out。
所以对于test.c
文件想要直接生成可执行程序test
,只需要一条命令就行:
gcc test.c -o test
动态库和静态库
对于一个库函数,以printf
为例,系统把这些函数实现都被做到名为libc.so.6
的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径/usr/lib
下进行查找,也就是链接到libc.so.6
库函数中去,这样就能实现函数printf
了,而这也就是链接的作用。
上面这种方式称为动态链接,依赖动态库。
函数库一般分为静态库和动态库两种:
- 静态库是指编译链接时,把库文件的代码全部加入到可执行文件当中,因此生成的文件比较大,但在运行时也就不再需要库文件了,静态库一般以
.a
为后缀。 - 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件当中,而是在程序运行时由链接文件加载库,这样可以节省系统的开销,动态库一般以
.so
为后缀。gcc 在编译时默认使用动态库。
所以,如果在链接的时候出现了链接错误,应该先确认是否存在对应的库。
使用file指令进行查看链接类型,使用ldd指令查看动态链接的可执行文件所依赖的库:
默认采用的是动态链接,但如果我们需要使用静态链接,在后面带上-static
选项即可:
静态链接在使用到库函数时,会将库函数的代码拷贝到程序当中,所以程序的体积是比较大的。
不难看出动态链接和静态链接的优缺点:
动态链接:
优点:程序的体积比较小,比较节省系统资源(磁盘的空间,内存的空间),bin体积小,加载速度快。
缺点:依赖动态库,程序可移植性较差(一旦库缺失,所有依赖这个库的程序都没法运行了)。
静态链接:
优点:不依赖第三方库,程序的可移植性较高(库缺失不影响程序的运行)。
缺点:程序的体积非常大,浪费空间。
在使用静态库之前可能需要先安装,命令如下:
sudo yum install glibc-static
sudo yum install libstdc++-static
调试器gdb
程序的发布方式有两种,debug模式和release模式:
- debug版本:程序本身会被加入调试信息,以便于进行调试。因此大小要比release版大。
- release版本:不会添加任何调试信息,是不可调试的。
Linux下gcc/g++后的二进制程序,默认是以release方式发布,如果要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g
选项以debug方式发布。
使用gdb进行调试:
gdb 文件名
进入进入调试(文件必须是debug版本)
调试方式和VS2019类似,只不过从图形化界面变成了命令行界面,在gdb中很多命令是可以简写的:
调试
run/r
:运行代码(启动调试)。next/n
:逐过程调试(不进入函数)。step/s
:逐语句调试(进入函数)。until 行号
:跳转至指定行。finish
:执行完当前正在调用的函数后停下来(不能是主函数)。continue/c
:运行到下一个断点处。set var 变量=x
:修改变量的值为x。回车
:重复上一条指令。
显示
list/l n
:显示从第n行开始的源代码,每次显示10行,若n未给出则默认从上次的位置往下显示.。list/l 函数名
:显示该函数的源代码。print/p 变量
:打印变量的值。print/p &变量
:打印变量的地址。print/p 表达式
:打印表达式的值,通过表达式可以修改变量的值。display 变量
:将变量加入常显示(每次停下来都显示它的值)。display &变量
:将变量的地址加入常显示。undisplay 编号
:取消指定编号变量的常显示。bt
:查看各级函数调用及参数。info/i locals
:查看当前栈帧当中局部变量的值。
断点
break/b n
:在第n行设置断点。break/b 函数名
:在某函数体内第一行设置断点。info breakpoint/b
:查看已打断点信息。delete/d 编号
:删除指定编号的断点。disable 编号
:禁用指定编号的断点。enable 编号
:启用指定编号的断点。
退出gdb
quit/q
:退出gdb。
补充内容
gcc 默认使用的是 C89 的标准,一些语法是C99才支持的,比如for(int i=0;i<n;i++)
中再for循环中定义变量i。因此用C98标准在编译的时候会报错,此时只需要加上-std=c99
即可使用C99的标准进行编译:
gcc test.c -o test -std=c99
Ctrl+r
可以联想历史输入过的指令。
readelf -S 文件名 | grep debug
可以查看程序的调试信息: