GCC编译详解

        GCC(GNU Compiler Collection--GNU编译器套件)可以编译C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言。GCC原本作为GNU操作系统的官方编译器,现已被大多数类Unix操作系统(如Linux、BSD、Mac OS X等)采纳为标准的编译器,现在GCC同样适用于微软的Windows。

        GCC的编译流程分为4个步骤:

· 预处理(Pre-Processing):产生.i文件

· 编译(Compiling):产生.s汇编文件

· 汇编(Assembling):生成.o的目标文件(即二进制目标代码)

· 链接(Linking)

        GCC所支持的后缀名有:

后缀名 含义
.c C源代码
.C/.cc/.cpp/.cxx C++源代码
.m ObjectC源代码
.i 已经过预处理的C原始程序
.ii 已经过预处理的C++原始程序
.s/.S 汇编语言的原始程序
.h 预处理文件(头文件)
.o 目标文件
.a/.so 编译后的库文件

        Gcc指令的一般格式为:Gcc [选项] 要编译的文件 [选项] [目标文件]
(其中,目标文件可缺省,Gcc默认生成可执行的文件,命为:编译文件.out)

常用编译选项如下表:

编译选项 含义
-E 生成.i文件:只进行预处理,不做其他处理
-S 生成.s汇编文件:只编译,不汇编
-c 生成.o目标文件:只汇编不链接
-g 在可执行程序中包含标准调试信息
-o file 输出到文件file中
-v 打印出编译器内部编译各过程的命令行信息和编译器的版本
-I dir 在头文件的搜索路径列表中添加dir目录
-L dir 在库文件的搜索路径列表中添加dir目录
-static 静态链接库
-l library
链接名为library的库文件
-w 关闭所有警告


我们以hello.c为例,来看一下gcc具体是如何编译的:

#include <stdio.h>
void main()
{
    printf("hello!gibson\n");
}


(1)预处理(Pre-Processing)阶段

在该阶段,编译器将上述代码中的stdio.h编译进来,并且用户可以使用Gcc的选项”-E”进行查看,该选项的作用是让Gcc在预处理结束后停止编译过程。

[gibson@localhost 桌面]$ gcc -v -E hello.c -o hello.i   #缺省情况下,自动用.i代替.c后缀名(此时文件名是hello而不是hello.i)

hello.i中部分内容:

# 1 "hello.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 28 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 361 "/usr/include/features.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 365 "/usr/include/sys/cdefs.h" 3 4
# 1 "/usr/include/bits/wordsize.h" 1 3 4
# 366 "/usr/include/sys/cdefs.h" 2 3 4
# 362 "/usr/include/features.h" 2 3 4
# 385 "/usr/include/features.h" 3 4
# 1 "/usr/include/gnu/stubs.h" 1 3 4
......
......
......
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__));

# 938 "/usr/include/stdio.h" 3 4

# 2 "hello.c" 2
void main()
{printf("hello!gibson\n");}



由此可见,预处理阶段将stdio.h头文件中的代码插入到了hello.c里面了。

        预处理主要作以下几种事情:
-.将所有的#define删除,并且展开所有的宏定义;
-.处理所有条件编译指令,如#if,#ifdef等;
-.处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。
-.删除所有的注释(//和/**/);
-.添加行号和文件标识,如 #2 “hello.c” 2 ,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
-.保留所有的#pragma编译器指令,因为编译器须要使用它们;

        include搜索文件时,会在以下几个路径搜索:
1.编译的时候指定路径
2.gcc的specs里
3.使用-I参数指定的路径
4.gcc环境变量设置(C_INCLUDE_PATH)

include "...":先在当前目录寻找,然后再系统目录寻找;
include <...>:只在系统目录寻找,找不到报错。
应用时,用前者包含用户自定义文件,用后者包含系统文件,这样的效率是最高的。

PS:系统目录应该是指gcc的specs里所指,形如:
/usr/include
/usr/local/include
/usr/lib/gcc-lib/i386-linux/2.95.2/include
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../include/g++-3
/usr/lib/gcc-lib/i386-linux/2.95.2/../../../../i386-linux/include

(2) 编译(Compiling)阶段

接下来进行的是编译阶段,在这个阶段中,GCC首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,GCC把代码翻译成汇编语言。用户可以使用”-S”选项来进行查看,该选项只进行编译(这里编译为名词)而不进行汇编(这个汇编为动词),生成汇编代码。

[gibson@localhost 桌面]$ gcc -S hello.i -o hello.s  #也可以用hello.c代替hello.i

hello.s文件全部内容如下:

	.file	"hello.c"
	.section	.rodata
.LC0:
	.string	"hello!gibson"
	.text
.globl main
	.type	main, @function
main:
	pushl	%ebp
	movl	%esp, %ebp
	andl	$-16, %esp
	subl	$16, %esp
	movl	$.LC0, (%esp)
	call	puts
	leave
	ret
	.size	main, .-main
	.ident	"GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
	.section	.note.GNU-stack,"",@progbits


(3)汇编(Assembling)阶段

汇编阶段是把编译阶段生成的”.s”文件转成目标文件(二进制文件),读者在此可使用选项”-c”就可看到汇编代码已转化为”.o”的二进制目标代码了(可用od命令查看二进制文件)。

[gibson@localhost 桌面]$ gcc -c hello.s -o hello.o #可以用hello.c代替hello.s

用od命令查看hello.o,内容如下:

0000000 042577 043114 000401 000001 000000 000000 000000 000000
0000020 000001 000003 000001 000000 000000 000000 000000 000000
0000040 000330 000000 000000 000000 000064 000000 000000 000050
0000060 000013 000010 104525 101745 170344 166203 143420 022004
... ...
... ...
... ...
0001440 000016 000000 000000 000000 000000 000000 000020 000000
0001460 064000 066145 067554 061456 066400 064541 000156 072560
0001500 071564 000000 000014 000000 002401 000000 000021 000000
0001520 004402 000000
0001524



(4)链接(Linking)阶段

在成功编译之后,就进入了链接阶段。链接阶段涉及到了一个重要概念:函数库

函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了(生成的可执行文件也不会再不受库的影响,即使库被删除了,程序依然可以成功运行)。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”。Gcc在编译时默认使用动态库。PS:函数库在/usr/lib目录下。


执行命令进行链接:

[gibson@localhost 桌面]$ gcc hello.o -o hello #可以用hello.c代替hello.o

此操作会在当前目录生成一个名为hello的可执行文件,然后执行命令

[gibson@localhost 桌面]$ ./hello

会显示出程序运行结果:hello!gibson

PS:-o file缺省情况下的文件名为a.out





你可能感兴趣的:(GCC编译详解)