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。
[lch@localhost test]$ gcc test.c -o test
[lch@localhost 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;
}
[lch@localhost 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
[lch@localhost test]$ gcc sin.c -o sin -lm # 需要链接上数学库libm.so
[lch@localhost 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>),头文件的搜索次序是:
- -I指定的目录,如果指定了多个目录,按照从左到右的顺序查找
- 标准头文件路径/usr/local/include
- 标准头文件路径/usr/include
按照顺序,使用最先找到的那个路径下的头文件;如果找不到就报错。
如果使用双引号指定头文件(#include “unp.h”),头文件的搜索次序是:
- 当前工作目录
- -I指定的目录,如果指定了多个目录,按照从左到右的顺序查找
- 标准头文件路径/usr/local/include
- 标准头文件路径/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进行调试,一些调试功能,例如列出程序源代码是无法进行的。
[lch@localhost 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进行调试,效果如下
[lch@localhost 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
选项,也就是没有任何优化。
优化有的时候是双刃剑,一些特殊的变量或情况在优化后可能有不同的执行结果。尤其是程序中存在汇编指令的时候。