C语言gcc编译器的使用

gcc是GNU C Compiler的简称(GNU C语言编译器)。它是GNU推出的性能优越的跨平台开源编译器。大多数Unix/Linux的程序都使用gcc进行编译,极大地促进了C语言在Unix/Linux上的使用。gcc可以编译C/C++语言,姊妹编译器g++编译C++程序会更加专业些。和gcc搭配使用的调试器是gdb。

快速上手

假设我们有一个C语言的程序,叫做test.c。我们希望编译出的可执行文件叫做test(Unix/Linux下可执行文件通常没有后缀名) 。最基本的使用方式是gcc 源文件名 -o 输出程序名。如果有多个源文件一起编译,将它们都列出即可。-o 输出程序名 这个部分指定了希望输出的文件名,如果不指定默认的输出文件叫a.out。

[[email protected] test]$ gcc test.c -o test
[[email protected] test]$ ./test
Test 6

如果程序中使用了动态库的库函数,在编译的时候需要链接上。最典型的例子是使用了math.h中的数学函数。我们对下面包含数学函数的程序进行编译。

#include <stdio.h>
#include <math.h>

#define PI 3.1416

int main(int argc, char const *argv[])
{
	double deg = 90;
	double s = 0;
	s = sin(deg / PI / 2);
	printf("sin(%lf)=%lf\n", deg, s);
	return 0;
}
[[email protected] test]$ gcc sin.c -o sin    # 普通编译无法通过,提示无法找到sin函数的定义
/tmp/ccgjNCqT.o: In function `main':
sin.c:(.text+0x44): undefined reference to `sin'
collect2: error: ld returned 1 exit status
[[email protected] test]$ gcc sin.c -o sin -lm  # 需要链接上数学库libm.so
[[email protected] test]$ ./sin
sin(90.000000)=0.982614

C程序编译的过程

我们通常说的“编译”一般包括4个步骤:预处理、真·编译、汇编、链接。前面使用的命令是将这4步一起执行完成的默认情况。

预处理是将程序代码进程重新整合,输出用于真·编译的程序代码。例如去掉注释内容,将宏定义进行展开,将头文件引入的内容进行展开等。生成文件的后缀名通常是.i。

真·编译是传统编译器完成的编译操作,它主要是根据语言的语法语义规则将程序代码(文本)生成机器可以执行的数据结构(语法树)并且生成中间代码(类编译格式)。生成的文件后缀名通常为.s。大部分编译时的优化都是在这步进行的。这部分的细节设计编译原理的知识,这里不做详细展开。

汇编是将中间代码生成机器可执行的二进制代码。这里得到的二进制程序已经具有在机器上运行的能力。生成的文件后缀名通常是.o。

链接是将程序中使用的各种库函数的实现找到并且组装到程序中。这样得到的程序代码就是完备的能够交付机器执行的。举个例子,基本Hello World程序中的printf函数是在头文件stdio.h中定义的,但是我们不知道它的具体实现方式。链接的目的就是找到库函数的具体实现的二进制代码,把它链接到我们程序中使用printf的地方。

gcc编译常用编译选项说明

基本上gcc编译器的命令格式是gcc [选项] 待编译的文件名(列表) [-o 目标文件名]选项-o 目标文件名都是选填项。不同的选项指定了gcc不同的编译行为。下面介绍常见的一些编译选项。

完成部分编译流程(-E/-S/-c)

  • 进行到预处理:选项是-E,针对C代码文件进行,生成文件后缀名通常为.i;命令样例gcc -E test.c -o test.i
  • 进行到真·编译:选项是-S,可对C代码或者预编译文件进行,生成文件后缀名通常为.s;命令样例gcc -S test.c -o test.s
  • 进行到汇编(常用):选项是-c,C代码、预编译文件和中间代码文件都可以进行,生成文件后缀名通常为.o;命令样例gcc -c test.c -o test.o

指定源文件类型(-x language/-x none)

默认情况下,gcc根据待编译文件的后缀名判断文件属于什么类型。比如.c对应C源代码,.cpp对应C++源代码,.s对应编译中间代码,.o对应二进制代码。对于不符合默认命名规则的文件,我们可以使用-x language filename(s)选项指定后面输入文件的语言类型,直到遇到-x none为止。

常见的language可以是:

  • c:C语言程序
  • c++:C++语言程序
  • objective-c:objective-c语言程序
  • c-header:C语言头文件
  • assembler:汇编语言
  • cpp-output:C语言二进制输出
  • c++-cpp-output:C++语言二进制输出
  • none:终止前面的指定语言选项,恢复根据后缀名判断文件类型的方式

举例,假设有一个标准C语言程序test1.c、自定义后缀名C语言程序test2.code、标准汇编程序test3.s。对三个文件进行编译,可以这样指定gcc -x c test1.c test2.code -x none test3.s -o test-x c 之后的所有源文件都会按照C语言编译,直到遇到-x none或者-x 新语言

指定头文件路径(-I)

这个选项可以指定想要搜索的头文件路径,可以方便头文件的管理或者覆盖掉默认的头文件。使用方式是-I headerPath.

如果使用尖括号指定头文件(比如#include <stdio.h>),头文件的搜索次序是:

  1. -I指定的目录,如果指定了多个目录,按照从左到右的顺序查找
  2. 标准头文件路径/usr/local/include
  3. 标准头文件路径/usr/include

按照顺序,使用最先找到的那个路径下的头文件;如果找不到就报错。

如果使用双引号指定头文件(#include “unp.h”),头文件的搜索次序是:

  1. 当前工作目录
  2. -I指定的目录,如果指定了多个目录,按照从左到右的顺序查找
  3. 标准头文件路径/usr/local/include
  4. 标准头文件路径/usr/include

同样地,按照顺序,使用最先找到的那个路径下的头文件;如果找不到就报错。

指定头文件(-include)

gcc可以在编译命令中通过-include header指定引入的头文件。即便是代码中没有出现头文件的情况下,也能引入头文件。如果在头文件中找不到函数的实现,不妨看一下程序的编译命令。

将前面出现的sin.c程序的#include <stdio.h>注释掉,我们使用gcc -include stdio.h sin.c -o sin -lm 便可以添加上stdio.h头文件并且编译程序。

相似的,gcc还提供了其他可以操控预编译环节的命令。例如-D name可以定义一个宏变量,-U name可以解除一个宏变量的定义。详情请参阅gcc官方文档的预编译选项

指定使用的外部库(-l)

如果程序使用了外部库(包括GNU标准库或第三方开发库),在链接步骤需要指定所有使用的函数库,否则会出现无法找到某个函数实现的错误。

-l选项需要紧跟库函数的名称,例如-lm代表使用的是数学库(math)。库文件的真实名称是libm.so,但是我们在-l选项中不需要写lib.so,gcc会自动补上。

生成gdb调试用的程序(-g)

使用-g选项生成的程序会包含额外的信息(例如函数名称),可以用于gdb的调试上。既然包括了额外的信息,-g选项生成的程序比普通方式生成的要大一些。

对上面编译过的sin.c程序使用gdb进行调试,一些调试功能,例如列出程序源代码是无法进行的。

[[email protected] test]$ gdb
# 中间省略
(gdb) file ./sin    # 加载程序
Reading symbols from /home/lch/test/sin...(no debugging symbols found)...done.  # 提示没有调试符号
(gdb) list   # 尝试列出程序代码
No symbol table is loaded.  Use the "file" command.  # 无法列出

我们加上-g选项编译,然后用gdb进行调试,效果如下

[[email protected] test]$ gdb
# 中间省略
(gdb) file ./sin
Reading symbols from /home/lch/test/sin...done.  # 读出了符号信息
(gdb) list    # 尝试列出源代码
1	// #include <stdio.h>
2	#include <math.h>
3	
4	#define PI 3.1416
5	
6	int main(int argc, char const *argv[])
7	{
8		double deg = 90;
9		double s = 0;
10		s = sin(deg / PI / 2);

控制输出信息的选项(-v/-Wall)

默认情况下,gcc只会输出编译过程中的错误信息。-v选项可以输出详细的编译步骤和信息,-Wall可以输出全部的警告信息(例如使用未初始化的变量)。

查看帮助(–help)

当记不得gcc的某个选项时,可以使用--help选项查看简要的帮助信息。详细的帮助信息可以使用man gcc查看。

指定优化等级(-O)

gcc指定了很多种对代码的优化方式,并且将这些优化方式组合成了多个以-O开头的编译选项。我们常用的有-O1, -O2-O3。这三个优化等级是依次加强的。默认使用-O0选项,也就是没有任何优化。

优化有的时候是双刃剑,一些特殊的变量或情况在优化后可能有不同的执行结果。尤其是程序中存在汇编指令的时候。

参考

发表评论