GCC编译器简介

时间:2022-10-11 02:04:47

转载自博客:http://www.cnblogs.com/iuices/archive/2011/12/10/2283618.html

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 <stdio.h>
intmain()
{
    intx;
    for(x=1;x<=10;x++)
    printf("%d\n",x);
    return0;
}

使用如下命令对上面的源文件进行预处理。

$ 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 "<built-in>"
# 1 "<命令行>"
# 1 "example.c"
 
# 1 "/usr/include/stdio.h" 1 3 4
...(中间部分省略)
externvoid funlockfile (FILE*__stream) __attribute__ ((__nothrow__));
# 936 "/usr/include/stdio.h" 3 4
 
# 2 "example.c" 2
intmain()
{
    intx;
    for(x=1;x<=10;x++)
 printf("%d\n",x);
    return0;
}

从上面的代码可以看出,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具有优化代码的功能,主要的优化选项包括如下:

  • -O0:不进行优化处理。
  • -O或-O1:进行基本的优化,这些优化在大多数情况下都会使程序执行得更快。
  • -O2:除了完成-O1级别的优化外,还要一些额外的调整工作,如处理器指令调度等,这是GNU发布软件的默认优化级别。
  • -O3:除了完成-O2级别的优化外,还进行循环的展开以及其他一些与处理器特性相关的优化工作。
  • -Os:生成最小的可执行文件,主要用于在嵌入式领域。

一般来说,优化级别越高,生成可执行文件的运行速度也越快,但消耗在编译上的时间就越长,因此在开发的时候最好不要使用优化选项,只有到软件发行或开发结束的时候才考虑对最终生成的代码进行优化。

  • -finline-functions:允许编译器将一些简单的函数在其调用处展开。
  • -funswitch-loops:将循环体中值不改变的变量移到循环体外。

具体的命令格式如下:

$ gcc –O2 –finline-functions example.c –o example

下面给出一个例子来看看GCC优化项的效果,源程序为:example6.c。

1
2
3
4
5
6
7
8
9
10
11
#include<stdio.h>
intmain ()
{
    intx;
    intsum=0;
    for(x=1;x<=le9;x++)
    {
        sum+=(x*300)/256;
    }
    return0;
}

首先不加任何优化选项,对上面的源程序进行编译如下:

$ gcc example6.c -o example6

$ time ./example6

real 0m4.750s

user 0m4.696s

sys 0m0.012s

time命令的输出结果由一下3部分组成:

  • real:程序的总执行时间,包括进程的调度、切换等时间。
  • user:用户进行执行的时间。
  • sys:内核执行的时间。

接下来使用优化选项-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 <stdio.h>
void main ()
{
    intx;
    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开头的选项,允许用户指定输出某个特定的警告,例如:

  • -Wcomment:出现注释嵌套时发出警告。
  • -Wconversion:如果程序中存在隐式类型转换,则发出警告。
  • -Wformat:检查printf和scanf等格式化输入输出函数的格式字符串和参数类型的匹配情况,如果发现不匹配则发出警告。
  • -Winline:如果函数不能被内联,则发出警告。
  • -Wlong-long:如果使用了long long型数据,则发出警告。
  • -Wmain:如果main函数的返回类型不是int型,或者调用main函数时使用的参数数目不正确,则发出警告。
  • -Wmissing-declarations:如果定义了全局函数,但却没有在头文件中声明,则发出警告。
  • -Wparentheses:在某些情况下,如果忽略掉了括号,则会发出警告。
  • -Wreturn-type:如果函数定义了返回类型,而默认类型是int型,编译器会发出警告。
  • -Wuninitialized:如果使用的自动变量没有被初始化,则发出警告。
  • -Wundef:如果在#if宏中使用了未定义的变量做判断,则发出警告。
  • -Wunused:如果声明的变量或static型函数没有使用,则发出警告。

下面使用GCC编译一段程序,来说明开启警告信息的必要性:

1
2
3
4
5
6
7
#include<stdio>
int main()
{
    doublex;
    printf("%d\n",x); /* 这里将%f误输为%d */
    return0;
}

对上面的程序进行编译:

$ 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<stdio>
int main()
{
    inta=1;
    intb=0;
    intc=1;
    if(a&&b||c)
    {
        ;
    }
    if(a==1)
        if(b==1)
            printf("b=1\n");
    else
        printf("b!=1\n");
    return0;
}

 

对上面的程序进行编译:

$ 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

  

       linux C [2]-gcc -lm

    使用math.h中声明的库函数还有一点特殊之处,gcc命令行必须加-lm选项,因为数学函数位于libm.so库文件中(这些库文件通常位于/lib目录下),-lm选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。本书用到的大部分库函数(例如printf)位于libc.so库文件中,使用libc.so中的库函数在编译时不需要加-lc选项,当然加了也不算错,因为这个选项是gcc的默认选项。


    C标准主要由两部分组成,一部分描述C的语法,另一部分描述C标准库。C标准库定义了一组标准头文件,每个头文件中包含一些相关的函数、变量、类型 声明和宏定义。要在一个平台上支持C语言,不仅要实现C编译器,还要实现C标准库,这样的实现才算符合C标准。不符合C标准的实现也是存在的,例如很多单 片机的C语言开发工具中只有C编译器而没有完整的C标准库。

在Linux平台上最广泛使用的C函数库是glibc,其中包括C标准库的实现。几乎所有C程序都要调用glibc的库函数,所以glibc是Linux平台C程序运行的基础。glibc提供一组头文件和一组库文件,最基本、最常用的C标准库函数和系统函数在libc.so库文件中,几乎所有C程序的运行都依赖于libc.so,有些做数学计算的C程序依赖于libm.so,以后我们还会看到多线程的C程序依赖于libpthread.so。以后我说libc时专指libc.so这个库文件,而说glibc时指的是glibc提供的所有库文件。

     glibc并不是Linux平台唯一的基础C函数库,也有人在开发别的C函数库,比如适用于嵌入式系统的uClibc




此时静态库文件中的代码全部包含到可执行文件中,所以生成的可执行文件比较大。