全部博文(842)
分类: LINUX
2012-05-12 21:15:37
1.GCC简介
GCC(GNU Compiler Collection)是一套功能强大、性能优越的编程语言编译器,它是GNU计划的代表作品之一。GCC是Linux平台下最常用的编译器,GCC原名 为GNU C Compiler,即GNU C语言编译器,随着GCC支持的语言越来越多,它的名称也逐渐变成了GNU Compiler Collection。下面对GCC的基本使用方法进行介绍。
2.基本选项GCC编译器的基本选项如下表:
类型 | 说明 |
-E | 预处理后即停止,不进行编译、汇编及连接 |
-S | 编译后即停止,不进行汇编及连接 |
-c | 编译或汇编源文件,但不进行连接 |
-o file | 指定输出文件file |
我们都知道程序的编译要经历预处理、编译、汇编以及连接4个阶段。在预处理阶段,主要处理C语言源文件中的#ifdef、#include、以及#define等命令。在与处理过程中,GCC会忽略掉不需要预处理的输入文件,该阶段会生成中间文件*.i。
对如下的源程序example.c:
1 2 3 4 5 6 7 8 | #include int main() { int x; for(x=1;x<=10;x++) printf("%d\n",x); return 0; } |
使用如下命令对上面的源文件进行预处理。
$ gcc -E example.c -o example.i
上面使用了两个选项:-E和-o file,其中-E表示在预处理结束后即停止编译过程;-o指定输出文件问file。前面的选项不同,输出的文件类型也不相同,可能为预处理后的C代码、汇编文件、目标文件或可执行文件,这里即为预处理后的C代码。
预处理后输出文件example.i的内容为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | # 1 "example.c" # 1 " # 1 "<命令行>" # 1 "example.c" # 1 "/usr/include/stdio.h" 1 3 4 ...(中间部分省略) extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__)); # 936 "/usr/include/stdio.h" 3 4 # 2 "example.c" 2 int main() { int x; for(x=1;x<=10;x++) printf("%d\n",x); return 0; } |
从上面的代码可以看出,GCC对源文件所包含的头文件stdio.h进行了预处理,由于输出文件example.i比较长,上面只给出了部分内容。
在编译阶段,输入的是中间文件*.i,编译后生成的是汇编语言文件*.s。对应的GCC命令为:
$ gcc -S example.i -o example.s
example.s即为生成的汇编文件,其内容为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | .file "example.c" .section .rodata .LC0: .string "%d\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushl %ebp .cfi_def_cfa_offset 8 .cfi_offset 5, -8 movl %esp, %ebp .cfi_def_cfa_register 5 andl $-16, %esp subl $32, %esp movl $1, 28(%esp) jmp .L2 .L3: movl $.LC0, %eax movl 28(%esp), %edx movl %edx, 4(%esp) movl %eax, (%esp) call printf addl $1, 28(%esp) .L2: cmpl $10, 28(%esp) jle .L3 movl $0, %eax leave .cfi_restore 5 .cfi_def_cfa 4, 4 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1" .section .note.GNU-stack,"",@progbits |
上面的语句主要实现了一个for循环,有汇编基础的朋友应该很容易看懂,这里不多介绍了。
上面的例子是在预处理后C代码的基础上进行编译的,其实可以直接从源代码编译,使用的命令为:
$ gcc –s example.c –o example.o
汇编是将输入的汇编语言文件转换为目标代码,可以使用-c选项来完成。对应GCC命令为:
$ gcc –c example.s –o example.o
最后,将生成的目标文件与其他目标文件(或库文件)连接成可执行的二进制代码文件。使用的命令为:
$ gcc example.o –o example
运行example,输出结果如下:
./example
1
2
3
4
5
6
7
8
9
10
以上代码从预处理、编译、汇编以及连接一步步介绍,主要目的是讲解程序编译的整个过程以及GCC的各个选项,其实如果只需要最终的可执行文件,可以直接对源代码进行编译连接,命令如下:
$ gcc example.c –o example
对于一个程序的多个源文件进行编译连接时,可以使用如下格式:
gcc –o test first.c second.c third.c
该命令将同时编译3个源文件,将它们连接成一个可执行文件,名为test。
上面的例子都给出-o选项,如果没有给出该选项,默认的输出结果为:预处理后的C代码被送往标准输出,即输出到屏幕,汇编文件为example.s,目标文件为example.o,而可执行文件问a.out。
3.优化选项GCC具有优化代码的功能,主要的优化选项包括如下:
一般来说,优化级别越高,生成可执行文件的运行速度也越快,但消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候才考虑对最终生成的代码进行优化。
具体的命令格式如下:
$ gcc –O2 –finline-functions example.c –o example
下面给出一个例子来看看GCC优化项的效果,源程序为:example6.c。
1 2 3 4 5 6 7 8 9 10 11 | #include int main () { int x; int sum=0; for(x=1;x<=le9;x++) { sum+=(x*300)/256; } return 0; } |
首先不加任何优化选项,对上面的源程序进行编译如下:
$ gcc example6.c -o example6
$ time ./example6
real 0m4.750s
user 0m4.696s
sys 0m0.012s
time命令的输出结果由一下3部分组成:
接下来使用优化选项-O2对上面的源程序进行处理:
$ gcc -O2 example6.c -o example6^C
$ time ./example6
real 0m1.473s
user 0m1.460s
sys 0m0.008s
从上面的结果可以看出,程序的性能得到了大幅度的改善。
4、警告选项
在编译过程中,编译器的报错和警告信息对于程序员来说是非常重要的信息,GCC包含完整的出错检查和警告提示功能,它可以帮助Linux程序员尽快找出错误的或潜在的错误代码,从而写过更优美的代码。GCC的编译器警告选项如下表:
类型 | 说明 |
-Wall | 启用所有警告信息 |
-Werror | 在发生警告时取消编译操作,即将警告看作是错误 |
-w | 禁用所有警告信息 |
下面看一段代码,使用GCC编译,同时开启警告信息:
1 2 3 4 5 6 7 8 9 | #include void main () { int x; for(x=1;x<=10;x++) { printf("%d\n",x); } } |
对上面的代码进行编译连接:
$ gcc -Wall example3.c -o example3
example3.c:2:6: 警告: ‘main’的返回类型不是‘int’ [-Wmain]
从上面的输出看到,GCC给出了警告信息,意思是main函数的返回值被声明为void,但实际应该是int。
此外,GCC还提供了许多以-W开头的选项,允许用户指定输出某个特定的警告,例如:
下面使用GCC编译一段程序,来说明开启警告信息的必要性:
1 2 3 4 5 6 7 | #include int main() { double x; printf("%d\n",x); /* 这里将%f误输为%d */ return 0; } |
对上面的程序进行编译:
$ gcc example4.c -o example4
可以看到,编译并没有报错,运行可执行文件,输出结果为:
$ ./example4
134513689
这不是想要的输出结果,如果在上面的编译中加入-Wformat或-Wall选项,即:
$ gcc -Wformat example4.c -o example4
或
$ gcc -Wall example4.c -o example4
GCC给出如下警告信息:
example4.c: 在函数‘main’中:
example4.c:5:5: 警告: 格式 ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat]
格式字符串和参数类型的不匹配会导致程序运行错误,所以这是是非常有用的警告选项。
下面使用GCC编译一段程序,使用-Wparentheses选项对其中的括号进行检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include int main() { int a=1; int b=0; int c=1; if(a&&b||c) { ; } if(a==1) if(b==1) printf("b=1\n"); else printf("b!=1\n"); return 0; } |
对上面的程序进行编译:
$ gcc -Wparentheses example5.c -o example5
example5.c: 在函数‘main’中:
example5.c:7:5: 警告: 建议在‘||’的操作数中出现的‘&&’前后加上括号 [-Wparentheses]
example5.c:11:7: 警告: 建议显式地使用花括号以避免出现有歧义的‘else’ [-Wparentheses]
所以GCC编译器的警告选项对程序员来说是非常重要的。
5、连接选项
GCC编译器提供的连接器选项如下表:
类型 | 说明 |
-Idirectory | 向GCC的头文件搜索路径中添加新的目录 |
-Ldirectory | 向GCC的库文件搜索路径中添加新的目录 |
-llibrary | 提示连接程序在创建可执行文件时包含指定的库文件 |
-static | 强制使用静态链接库 |
-shared | 生成动态库文件 |
先来理解一下头文件和库文件这两个概念:
头文件包含变量和函数的声明,但没有定义函数的实现。函数的具体实现实在库文件中完成的,库文件可分为静态库和动态库,静态库是指编译连接时,将库 文件的代码全部加入到可执行文件中,这样运行时就不需要库文件了。静态库的后缀名一般为“.a”。动态库是指在编译连接时并不将库文件的代码加入到可执行 文件中,而是在程序执行时由运行时连接文件加载库文件,这样可以节省系统的开销。动态库的后缀名一般为“.so”。
例如我们编译是用-I选项来指定头文件的路径:
$ gcc example.c –o example –I/home/xxx/include
头文件所对应的库文件,如果没有特别指定时,GCC会到默认的搜索路径进行查找。
使用-L选项来指定库文件的路径,例如:
$ gcc example.c –o example –L/home/xxx/lib
GCC编译器在默认情况下使用动态库,但如果使用了-static选项,连接器将忽略动态库,强制使用静态链接库,即使用如下命令:
$ gcc example.c –o example –static –lm
此时静态库文件中的代码全部包含到可执行文件中,所以生成的可执行文件比较大。