GCC的基本使用

1. GCC简介

GCC的全称是GNU Compiler Collection,它是一个能够编译多种语言的编译器。最开始GCC是作为C语言的编译器(GNU C Compiler),经过了这么多年的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持 Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。

2. 简单编译

这里以一个hello.c文件为例,介绍gcc的基本使用

//hello.c
#include <stdio.h>
int main(void)
{
    printf("hello gcc");
    return 0;
}

使用gcc编译hello.c,生成可执行文件hello

gcc hello.c -o hello

上面的编译过程,分为四个阶段进行:预处理(Pre-Processing)、编译(Compiling)、汇编(Assembling)、链接(Linking)。其过程如下图所示

2.1 预处理

首先,对hello.c进行预处理:

gcc -E hello.c -o hello.i 或 gcc -E hello.c

执行上面的命令,可以输出hello.i文件,该文件仍然是文本文件,里面存放了预处理过后的hello.c的代码。
预处理的主要作用包括三个:
(1)将源文件中以"include"格式包含的文件复制到编译的源文件中;
(2)用实际值替换"#define"定义的字符串;
(3)根据"#if"后面的条件决定需要编译的代码。
gcc的-E选项,指示编译器在预处理后停止,并输出预处理的结果。-o选项表示输出预处理文件,这里命名为hello.i。在本例中,预处理结果是将stdio.h中的内容插入到hello.c中。

2.2 编译

经过预处理,可以直接对生成的hello.i文件进行编译,生成汇编代码:

gcc -S hello.i -o hello.s

gcc的-S选项,在编译期间,指示编译器在生成汇编代码后停止,-o输出汇编代码文件hello.s

2.3 汇编

对于上面生成的汇编代码文件hello.s,gas汇编器负责将其编译为目标文件hello.o,命令如下:

gcc -c hello.s -o hello.o

2.4 链接

对于上面生成的目标文件hello.o,gcc链接器将其与C标准输入输入库进行链接,最终生成可执行文件hello

gcc hello.o -o test

2.5 小结

归纳一下上面的过程

命令 含义
gcc -E hello.c -o hello.i 预处理
gcc -S hello.i -o hello.s 编译
gcc -c hello.s -o hello.o 汇编
gcc hello.o -o hello 链接
gcc hello.c -o hello 直接编译链接成可执行目标文件
gcc -c hello.c或gcc -c hello.c -o hello.o 编译生成可重定位目标文件

3.gcc常用选项

经过上面的例子,相信大家对gcc的使用由基本的了解,下面介绍一些gcc的常用选项

选项名 作用
-o 产生目标(.i、.s、.o、可执行文件等)
-c 通知gcc取消链接步骤,即编译源码并在最后生成目标文件
-E 只运行C预编译器
-S 告诉编译器产生汇编语言文件后停止编译,产生的汇编语言文件扩展名为.s
-Wall 使gcc对源文件的代码有问题的地方发出警告
-Werror 使gcc将所有warning当成error处理
-Idir 将dir目录加入搜索头文件的目录路径
-Ldir 将dir目录加入搜索库的目录路径
-llib 链接lib库
-g 在目标文件中嵌入调试信息,以便gdb之类的调试程序调试

4.多文件编译

上面的例子是单个源文件的编译过程,接下来我们来看看一个多文件编译的例子:

//hello_fn.h
#ifndef hello_fn_h
#define hello_fn_h
void hello_fn();
#endif
//hello_fn.c
#include <stdio.h>
void hello_fn()
{
    printf("hello gcc");
}
//main.c
#include "hello_fn.h"
int main(void)
{
    hello_fn();
    return 0;
}

可以使用下面的命令,一次性对多个源文件进行,得到可执行文件newhello:

gcc  hello_fn.c main.c –o newhello

当然,也可以独立对每个源文件进行编译独立编译,如下:

gcc -Wall -c main.c -o main.o
gcc -Wall -c hello_fn.c -o hello_fn.o
gcc -Wall main.o hello_fn.o -o newhello

gcc的-Wall选项,能够使gcc产生尽可能多的警告信息,如果有的话。
注意,gcc给出的警告信息虽然严格意义上说不能算作错误,但却有可能成为错误的栖身之所,为了避免此种情况的发生,可以将警告信息当成编码错误来对待!在编译时带上-Werror选项,那么gcc会在所有产生警告的地方停止编译,迫使我们对代码进行检查,具体如下:

gcc -Werror -c hello_fn.c -o hello_fn.o

5.库文件链接

开发软件时,通常需要借助许多函数库的支持才能够完成相应的功能,比如我们上面使用的C标准输入输出库。实际上,函数库就是一些头文件(.h)和库文件(.so、.a)的集合,Linux下的大多数函数都默认将头文件放在/usr/include/目录下,而库文件则放在/usr/lib/目录下。

5.1 静态库和共享库

Linux的库文件分为两大类——静态库(通常以.a结尾)和共享库(通常以.so结尾),二者的区别在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
静态库:程序在编译链接的时候就把库的代码链接到可执行文件中。因此程序运行的时候将不再需要静态库
共享库:程序在运行的时候才去链接共享库的代码,多个程序共享使用库的代码

说明:1.库文件其实是.o文件的归档!通过将.o文件打包而生成库文件.a或.so
2.在链接库时, -lm表示要链接libm.so(共享库)或者libm.a(静态库)库文件
3.一个与共享库链接的可执行文件仅仅包含它用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码
4.在可执行文件开始运行以前,外部函数的机器码由操作系统从磁盘上的该共享库中复制到内存中,这个过程称为动态链接(dynamic linking)
5.共享库可以在多个程序间共享,所以动态链接使得可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份共享库被要用到该库的所有进程共用,节省了内存和磁盘空间。

下面简单介绍一下静态库和共享库的生成和使用!

5.2 生成静态库的过程

hello_fn.h
hello_fn.c
main.c
gcc -Wall hello_fn.c -o hello_fn.o
ar rcs libhello.a hello_fn.o // 将.o文件归档为一个库libhello.a

ar是gnu的归档工具,rcs(replace、create、save保存.o 文件的一些索引到库文件中)

5.3 使用静态库

gcc -Wall main.c libhello.a -o main    // 方法1
gcc -Wall -L. main.o -o main -lhello    // 方法2

注意:
(1) -lhello 表示链接libhello.a库,这里省略了lib和后缀名.a,只取库的名称即可
(2) -L. 表示在当前的目录下(.)查找链接库!否则会报错:找不到-lhello

5.4 生成共享库的过程

gcc -shared -fPIC hello_fn.o -o libhello.so

shared:表示生成共享库格式
fPIC:产生位置无关码(position independent code),表示库加载到内存中位置任意
库命名规则:libxxx.so
注意:hello_fn.o 文件的在生成的过程中需要指定为-fPIC,否则再生成共享库的过程中会报错,提示recompile with -fPIC

5.5 使用共享库

gcc main.o -o main -L -lhello

注意:
(1)当可执行文件运行时需要使用共享库时,必须能够从库搜索目录找到对应的库文件,否则出错
(2)三种运行共享库的方法
a. 拷贝.so文件到系统共享库路径下,一般指/usr/lib
b. 更改LD_LIBRARY_PATH
c. ldconfig配置ld.so.conf文件,然后使用ldconfig命令更新ld.so.cache

5.6 小结

关于共享库和静态库,需要注意:
(1)当.a和.so同时存在于同一搜索目录下时,优先选择共享库.so
(2)使用ldd可以查看可执行文件在执行的时候需要加载的库(.so)、
(3)-I指定头文件的搜索路径,-L指定库的搜索路径
(4)链接的时候需要使用-Ldir给出搜索路径,使用-lname给出链接库名

posted @ 2017-02-10 20:32  west000  阅读(549)  评论(0编辑  收藏  举报