开发者通常选择GCC来编译C语言编写的源代码,选择G++来编译C++源代码。
GCC/G++编译器没有图形界面,只能在终端上以命令方式运行。编译命令由命令名、选项和源文件名组成,格式如下所示:
gcc [-选项 1] [-选项 2]…[-选项 n] <源文件名>
g++ [-选项 1] [-选项 2]…[-选项 n] <源文件名>
命令名、选项和源文件名之间使用空格分隔,一行命令中可以有多个选项,也可以只有一个选项。文件名可以包含文件的绝对路径,也可以使用相对路径。如果文件名中不包含路径,那么源文件被视为存在于工作目录中。如果命令中不包含输出的可执行文件名称,默认情况下将在工作目录中生成后缀为“.out”的可执行文件。
常用的GCC和G++编译选项见表所示:
GCC和G++编译器执行过程可总结为四步:预处理、编译、汇编、连接。
1.预处理,生成.i的文件[预处理器cpp]
2.将预处理后的文件转换成汇编语言,生成文件.s[编译器egcs]
3.由汇编变为目标代码(机器代码)生成.o的文件[汇编器as]
4.连接目标代码,生成可执行程序[链接器ld]
以前我们常用gcc main.c -o main命令编译一个程序,其实也可以分三步做,第一步生成汇编代码,第二步生成目标文件,第三步生成可执行文件:
$gcc -S main.c$ gcc -c main.s$ gcc main.o
-S选项生成汇编代码,-c选项生成目标文件, -E选项只做预处理而不编译,如果不加这些选项则gcc执行完整的编译步骤,直到最后链接生成可执行文件为止。如下图所示。
这些选项都可以和-o搭配使用,给输出的文件重新命名而不使用gcc默认的文件名(xxx.c、xxx.s、xxx.o和a.out),例如gcc main.o -o main将main.o链接成可执行文件main。
-v可以了解详细的编译过程。
一个好的习惯是打开gcc的-Wall选项,也就是让gcc提示所有的警告信息,不管是严重的还是不严重的,然后把这些问题从代码中全部消灭。
gcc main.c(编译)
eg: gcc a.c b.c
gcc –E main.c –o main.i (生成预处理后的文件)
gcc -S main.c -o main.s(生成汇编指令,catmain.s可查看)
gcc main.c -o main(生成可执行文件main, ./main执行)
也可编译后直接./a.out 默认生成这个可执行文件(因为当前的工作目录已经是“/home/用户名/mainworld”了,所以可用“./”替代工作目录的路径)
gcc main.s
gcc -c main.c –o main.o生成.o 文件.o 结尾为机器语言
gcc main.c –o main –DMY_DEBUG 加入调试信息,加入后,会多出Enter 和 Leave
如下程序
#include
#define MY_DEBUG 1 //gcc中不加-D的话这里define一下
int main(int argc,char *argv[])
{
#if MY_DEBUG
printf("Enter %s\n",__func__);
#endif
int i;
printf("hello\n");
#if 0
printf("world\n");
//for (i=0;i // printf("%s\n",argv[i]); #endif #if MY_DEBUG printf("Leave %s\n",__func__); #endif return 0; } gcc -E x.c -o x.i cat x.i -E选项,表示让gcc只进行“预处理”就行了。 所谓的预处理,就是把程序中的宏展开, 把头文件的内容展开包含进来等等一些编译前的预处理操作。 预处理 cpp x.c(文件名) -o x.i 如果目标文件是由C代码编译生成的,用gcc做链接就没错了(汇编不能用gcc来编译),整个程序的入口点是crt1.o中提供的_start,它首先做一些初始化工作(以下称为启动例程,StartupRoutine),然后调用C代码中提供的main函数。所以,以前我们说main函数是程序的入口点其实不准确,_start才是真正的入口点,而main函数是被_start调用的。 如果分两步编译,第二步gcc main.o –o main其实是调用ld做链接的,相当于这样的命令: $ ld/usr/lib/crt1.o /usr/lib/crti.o main.o -o main -lc -dynamic-linker/lib/ld-linux.so.2 gcc的编译优化选项有-O0、-O、-O1、-O2、-O3、-Os几种。-O0表示不优化,这是缺省的选项。-O1、-O2和-O3这几个选项一个比一个优化得更多,编译时间也更长。-O和-O1相同。-Os表示为缩小目标代码尺寸而优化。 使用math.h中的函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(通常在/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。本书用到的大部分库函数(例如printf)位于libc.so库文件中,以后称为libc,使用libc中的库函数在编译时不需要加-lc选项,当然加了也不算错,因为这个选项是gcc默认的。 gdb 调试工具 GDB调试器调试的对象是可执行文件,使用GCC或G++编译器编译源代码时,必须加上选项“-g”才能使目标可执行文件包含可被调试的信息。 b 12 if i == 4 断点可以设置条件 此句表示i等于4中断 x/7b input x命令打印存储器中的内容。7b是打印格式,b表示每个字节一组,7表示打印7组 其中像 break(b) list(s) run(r) print(p) quit(q)next(n) step(s) continue(c)等都可用简写 eg:调试hello可执行文件 gcc hello.c -o hello –g (gdb) hello (gdb) l (list显示源程序) (gdb) l (gdb)b 6 在某一行设置断点Breakpoint .. b main main开始 (gdb)r 运行,设置断点后 (gdb)p打印检查常量c的值可输入下列命令:(gdb) print c// 显示变量c的值 (gdb)n下一句运行 (gdb)s进到函数里面运行(没函数和n一样) (gdb)q退出 bt(baketrace) frame 1 (选择1号栈帧) set var sum = 50 (修改某变量的值) finish 执行到函数返回,观察返回值(return value) 如果某个函数中发生访问越界,很可能并不立即产生段错误,而在函数返回时却产生段错误. DEBUG调试快速定位,gdb调试逐条细化调试。 在头文件中定义MY_DEBUG即可 #define MY_DEBUG 可在代码中加入下列语句 #ifdef MY_DEBUG printf("enterinto func %s\n",__func__); #endif #ifdef MY_DEBUG printf("leavefunc %s\n",__func__); #endif disassemble可以反汇编当前函数或者指定的函数,单独用disassemble命令是反汇编当前函数,如果disassemble命令后面跟函数名或地址则反汇编指定的函数。 以前我们讲过step命令可以一行代码一行代码地单步调试,而这里用到的si命令可以一条指令一条指令地单步调试。 info registers可以显示所有寄存器的当前值。 在gdb中表示寄存器名时前面要加个$,例如p $esp可以打印esp寄存器的值,在上例中esp寄存器的值是0xbff1c3f4,所以x/20 $esp命令查看内存中从0xbff1c3f4地址开始的20个32位数。3.1.2 GDB调试器
调试方法:
几个新的gdb命令: