浅谈gcc编译

引言

gcc是GNU Compiler Collection(GNU编译器套装),是一套由GNU(Gnu's Not Unix) 开发的编程语言编译器。gcc是Linux操作系统下最常用的编译程序,它甚至是Linux平台编译器的事实标准。由gcc的manual可以看出, 其主要用于编译C及C++语言。本文以C语言为例,希望用最浅显易懂的方式谈谈GCC的通常用法。

这里摘抄了gcc manual的一小部分:有一定gcc使用经验的人可以作为参考;不太熟悉gcc的人可以绕过这里,直接跳到第1节——最简单的例子。

 $ man gcc    #comments: 这里表示在linux shell下敲入man gcc,以下类似。

NAME
       gcc - GNU project C and C++ compiler

SYNOPSIS
       gcc [-c│-S│-E] [-std=standard]
           [-g] [-pg] [-Olevel]
           [-Wwarn...] [-pedantic]
           [-Idir...] [-Ldir...]
           [-Dmacro[=defn]...] [-Umacro]
           [-foption...] [-mmachine-option...]
           [-o outfile] infile...

       Only the most useful options are listed here; see below for the remain-
       der.  g++ accepts mostly the same options as gcc.

       ......

 

1. 最简单的例子(编译单个源文件)

首先,我们来看一个最简单的例子。通过编辑器(linux下常用的是emacs及vi)编写一个叫做example_1.c的源程序,内容如下:

#include <stdio.h>
int main()
{
  printf("You are welcome to my blog!\n");
  return 0;
}


我们可以用GCC来编译以上源文件,以生成可执行文件exec(我们可以把这种编译方式成为“一步到位”,这种命名方式来自博客http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2287738.html)。

$ gcc example_1.c -o exec

这里的-o选项表示后面是输出文件名。(有些刚刚接触gcc的人(包括笔者)可能会以为-o表示后接目标文件*.o,这里需要稍微注意一下哦:)。)

这样,我们生成了可执行文件exec,运行它可以输出“You are welcome to my blog!”:

$ ./exec

You are welcome to my blog!

实际上,以上编译过程是分为四个阶段进行的,它们分别是:预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)以及连接(Linking)。gcc的编译选项-E、-S、-c可以分别使编译过程在预处理、编译、汇编后停止。换句话说,我们可以将以上的“一步到位”拆分成以下四步:

$ gcc -E example_1.c -o example_1.i     #预处理,生成中间文件example_1.i;完成#include(将.h插入.c文件)、#define、#ifdef等预处理命令

$ gcc -S example_1.i -o example_1.s    #编译,生成汇编语言文件example_1.s

$ gcc -c example_1.s -o example_1.o   #汇编,生成机器语言文件(目标文件)example_1.o

$ gcc example_1.o -o exec                       #连接,生成可执行二进制文件exec

一般情况下,-E及-S选项很少用到,我们常用的编译除了前面提到的“一步到位”方式外,还包括下面的“两步到位”方式:

$ gcc -c example_1.c -o example_1.o     #编译(实际包括预处理、编译及汇编),生成机器语言文件(目标文件)example_1.o

$ gcc example_1.o -o exec                        #连接(将多个目标文件、库文件连接在一起),生成可执行二进制文件exec

有些读者可能会有疑问:既然我们有了“一步到位”的编译方式,“两步到位”的方式还有它的用武之地吗?当然是有的啦。读完第2节,就能找到答案啦微笑。这里不是广告,确实是第2节中的例子才能更好地阐释这种“用武之地”。


2. 次简单的例子(编译多个源文件)

我们的程序变得稍微复杂了一点,包括两个源文件example_2_main.c、example_2_sub_func.c,以及一个头文件example_2_sub_func.h,它们一起完成了向标准输出(计算机屏幕)打印字符串"You are welcome to my blog!"。 目录树结构及源程序如下:

example_2_main.c:

#include "example_2_sub_func.h"

int main()
{
  print_welcome();

  return 0;
}

example_2_sub_func.c:

#include <stdio.h>

void print_welcome()
{
  printf("You are welcome to my blog!\n");
}

example_2_sub_func.h:

void print_welcome();

我们可以使用“一步到位”的编译方式:

$gcc example_2_main.c example_2_sub_func.c -o exec

注:c语言里每一个源文件(.c文件)为一个编译单元;头文件并不是一个单独的编译单元,其内容会在预处理(preprocessing)过程中被插入到包含(#include)它的源文件中。

上面的编译过程同样可以拆分成“两步到位”的编译方式:

$ gcc -c example_2_main.c -o example_2_main.o                     #编译源文件example_2_main.c,生成目标文件example_2_main.o

$ gcc -c example_2_sub_func.c -o example_2_sub_func.o      #编译源文件example_2_sub_func.c,生成目标文件example_2_sub_func.o

$ gcc example_2_main.o example_2_sub_func.o -o exec         #连接目标文件example_2_main.o、example_2_sub_func.o,生成可执行文件exec

这样,读者可能能看出“两步到位”编译方式的好处啦:当程序包括多个编译单元的时候,如果其中一个编译单元发生改变,则只需要重新编译该单元,并连接生成可执行文件就行了。例如,这个例子包含两个编译单元:example_2_main.c及example_2_sub_func.c;如果example_2_main.c发生了改变,则我们仅需要重新编译该源文件并重新连接。这样可以避免当工程太大的时候编译太耗时。哈哈,这里回答第1节末尾的问题啦:)。

3.  稍复杂的例子(编译多个不在同一目录下的文件)

为了项目管理的需要,我们的源程序通常位于不同的目录下。例如我们把第2节例子中的example_2_main.c放在目录./main/下,而把example_2_sub_func.c及example_2_sub_func.h放在目录./sub_func/下。目录树结构如下:

在这种情况下,我们的“一步到位”编译命令为:

$ gcc -I./sub_func ./main/example_2_main.c ./sub_func/example_2_sub_func.c -o exec

其中-I(i的大写)为编译选项,表示头文件的搜索路径。这里头文件的搜索路径为./sub_func.

当然,以上的编译过程可以拆分为:

$ gcc -c ./sub_func/example_2_sub_func.c -o example_2_sub_func.o   #编译源文件example_2_sub_func.c

$ gcc -c -I./sub_func ./main/example_2_main.c -o example_2_main.o    #编译源文件example_2_main.c,其依赖位于目录./sub_func/下的头文件

$ gcc example_2_main.o example_2_sub_func.o -o exec                          #连接目标文件example_2_main.o、example_2_sub_func.o,生成exec

4. 较复杂的例子(连接库文件)
开发应用程序的时候,我们常常需要使用第三方函数库。函数库实际上是一些头文件(.h)和库文件(静态库.a或动态库.so)的集合。如果使用静态库,则静态库代码在编译(连接阶段)时就拷贝到了程序的代码段,程序的体积会膨胀;如果使用动态库,则程序中只保留库文件的名字和函数名,在运行时去查找库文件和函数体,程序的体积基本变化不大,但增加了程序的运行时间。

第5节会介绍生成库文件的方法,这一节我们学习如何使用库文件。

还是使用第2节中的程序作为例子。这里我们将example_2_main.c放在./main/目录下;头文件example_2_sub_func.h放在./sub_func/include/目录下;源文件example_2_sub_func.c不再存在,取而代之的是静态库文件libexample_2_sub_func.a(注意,静态库文件名以lib开始,以.a结束),其放在./sub_func/lib/目录下。目录树结构如下:

“一步到位”的编译方式为:

$ gcc -I./sub_func/include -L./sub_func/lib ./main/example_2_main.c -lexample_2_sub_func -o exec

其中-L为连接选项,后接库的搜索路径;-l(L的小写形式)也是连接选项,后接库的名字(去掉前缀lib及后缀.a)。注意:这里-l(L的小写形式)应该放在源文件./main/example_2_main.c的后面,否则无法连接成功。

类似地,“两步到位”的编译方式为:

$ gcc -c -I./sub_func/include ./main/example_2_main.c -o main.o

$ gcc -L./sub_func/lib main.o -lexample_2_sub_func -o exec

5. 如何生成库文件

生成静态库用ar命令:ar为归档命令,将多个.o文件归档在一起形成库文件,例如:

$ ar -rc libname.a a.o b.o #静态库文件名以lib为前缀,.a为后缀

生成动态库(共享库)用gcc,需加上-shared和-fpic选项:

$ gcc -fpic -shared -o libname.so name.c #动态库以lib为前缀,.so为后缀


6. 结尾

希望通过以上的方式能够让读者对linux下的gcc编译方式有一个初步的了解。

 

参考文献:

本文参考了gcc manual、维基百科、网络博客http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2287738.htmlhttp://developer.51cto.com/art/200609/32317.htm以及其他一些资料。


 


 

 

 


 

 

 

 

 



 






 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值