Linux之GCC的使用

文档说明:

本文大部分内容翻译自GCC官方手册(参考自GCC4.6.1手册),尤其是“GCC命令行选项”一节。如有翻译不当,请指明。

本文参考书目:

《Using the GNU Compiler Collection》(For GCC version 4.6.1)。

早期,GCC是“GNU C Compiler”之意,单指GNU生产的“C语言编译器”;现在,GCC是“GNU Compiler Collection”之意,指GNU生产的“众多编程语言的编译器集合”。

1、GCC所支持的常见后缀名

suffix state
.a 静态库(文档)
.c 需要预处理的C语言源代码
.h C/C++语言源代码的头文件
.i 经过预处理后的C语言源代码
.o 目标文件(经过汇编产生的)
.s 经过编译后产生的汇编语言代码(不需要再进行预处理)
.S .sx 需要进程预处理的汇编语言代码
.so 动态共享库
.C .c++ .cc .cpp .cp .cxx .CPP 需要预处理的C++源代码
.hh .hpp .H .hp .hxx .HPP .h++ .tcc C++头文件

2、GCC的编译流程

GCC的编译流程分为4个阶段:

(1) 预处理(Preprocessing)

$ gcc -E file.c -o file.i

注:如果没有“-o file.i”,其结果只会输出到标准输出。

(2) 编译(Compiling):生成汇编代码

$ gcc -S file.c

注:上述命令会自动生成file.s文件(不用添加“-o”选项)。如果编译多个源文件,会为每个源文件都生成一个汇编语言模块。

(3) 汇编(Assembling):生成目标代码

$ gcc   -c  file.c

注:上述命令会自动生成file.o文件(不用添加“-o”选项)。

(4) 链接(Linking):生成二进制可执行程序

$ gcc file.o          // 生成a.out文件
$ gcc file.o -o file  // 生成file文件

注:生成最终的可执行文件的命令如果没有用“-o”选项指定,则默认输出为a.out

以上四步可以单独执行,也可以合并在一起形成一条命令执行(即,不添加任何“-E”、“-S”或“-c”选项)。

3、库文件的制作与使用

(1) 创建静态库

静态库是一些.o文件的集合,它们是由编译程序按照通常的方式生成的。在编译链接时,链接器会把静态库文件的代码全部加入到可执行文件中,因此,生成的文件比较大,但在运行时也就不再需要库文件了,运行效率相对较快些。将程序连接到库中的目标文件和将程序连接到目录中的目标文件是一样的。静态库的另一个名字是文档(archive),而管理这些文档内容的工具叫做ar

制作静态库的一般步骤是

第一步,先生成目标文件(即.o文件): $ gcc -c file1.c file2.c
第二步,使用ar工具创建静态库:      $ ar -r libfile.a file1.o file2.o

注:静态库的命名习惯以lib开头、以.a为后缀。

(2) 静态库的使用

将程序和静态库链接起来的方式有两种:

第一种: 在链接命令中指定静态库的名称:

$ gcc -o prog prog.c libdemo.a

第二种: 将静态库放在链接器搜索的其中一个标准目录中,然后使用-l选项指定库名(即库的文件名去除了lib前缀和.a后缀):

$ gcc -o prog -ldemo

(3) 创建动态共享库

与静态库相反,动态库在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载动态库,这样可节省系统的开销。但相对静态库来说,效率会低一些。

动态库是目标文件的集合,但是这些目标文件是由编译程序按照特殊文件方式生成的。对象模块的每个地址(变量引用和函数调用等)都是相对地址,不是绝对地址。因此允许在运行程序时,可以动态地加载和执行共享库模块。

虽然共享库的代码是由多个进程共享的,但其中的变量却不是的。每个使用共享库的进程会拥有自己的库中定义的全局和静态变量的副本。

制作动态库的一般步骤是

**第一步:**先生成目标文件(即.o文件,但要添加-fpic选项):

$ gcc -c -fpic file1.c file2.c

其中,-c指出编译器要生成.o目标文件,-fpic使得输出的对象模块是按照可重定位地址方式生成的,缩写pic代表位置独立代码(Position Independent Code)。

**第二步:**使用“-shared”选项生成动态库:

$ gcc -shared file1.o file2.o -o file.so

其中,-shared选项指出生成动态共享库。

说明:以上两条命令步骤可以合并成一个:

$ gcc -fpic -shared file1.c file2.c -o file.so

注:在生成最终的动态共享库(即上述的file.so)时,一定要使用“-o”选项,否则,默认生成a.out格式。

以上是C语言的一般制作方法,但对于C++同样适用,只需要gcc换成g++,把其C文件换成C++文件即可。

(4) 共享库的使用

为了使用一个共享库就需要做两件事情(使用静态库的程序则无需完全这两件事情):

(1)将程序与共享库链接起来时自动会将库的名字嵌入可执行文件中;
(2)动态链接,即在运行时解析内嵌的库名(这个任务是由动态链接器来完成的)。

注:动态链接器本身也是一个共享库,共名称是 /lib/ld-linux.so.2

注:GCC在链接时优先考虑共享库,其次才是静态库,如果希望GCC只考虑静态库,可以指定-static选项。

4、GCC命令行选项

(1) 通用选项

-x language

为后面的输入文件显式地指定语言(而不是让编译器根据文件后缀挑选一个默认的)。
这个选项作用于下一个 -x 选项前的所有输入文件。该选项的有效值有:
   c   c-header    cpp-output            c++  c++-header   c++-cpp-output
   objective-c     objective-c-header    objective-c-cpp-output
   objective-c++   objective-c++-header  objective-c++-cpp-output
   assembler       assembler-with-cpp
   ada
   f77     f95     f77-cpp-input      f95-cpp-input
   go
   java

-x none

关闭任何语言的指定,后序的文件将重新根据文件后缀来处理。

-c

编译或汇编源文件,但不链接,最终输出目标文件(.o)。

-S

在编译后停止,不进程汇编(即只编译、不汇编),其输出的是汇编源代码文件(.s,不需要再进行预处理)。

-E

在预处理后停止,不进行编译(即只预处理、不编译)。
其输出默认打印到标准输出,必须指定“-o”选项才能把预处理后的源代码输出到一个文件(.i或.ii)中。

-o file

把执行结果输出到file文件中,该选项可以用于任何阶段。

-v

把在编译阶段所要执行的命令打印到标准错误输出。

-###

同 -v 选项,但有区别,如:当前命令不会被执行。

–help

打印帮助信息到标准输出。

–version

打印GCC的版本号和版权。

(2) C语言选项

-ansi

在C模式下,等价于 “-std=c90”;在C++模式下,等价于 “-std=c++98”。
该选项将关闭一些与ISO C90或C++标准不兼容的GCC特性。

-std=

决定语言的标准。该选项仅仅支持C/C++,其有效值有:
c90  c89  iso9899:1990    // ISO C90标准
iso9899:199409            // ISO C90修订案1
c99  c9x  iso9899:1999  iso9899:199x  // ISO C99标准,该标准没有完全被支持且“c9x”和“iso9899:199x”被废弃。
c1x           // ISO C1x标准(下一个ISO C标准)
gnu90  gnu89  // ISO C90的GNU方言(包括一些C99特性),这是默认选项。
gnu99  gnu9x  // ISO C99的GNU方言。当ISO C99完全被实现时,该选项将是默认选项,且“gnu9x”被废弃。
gnu1x         // ISO C1x的GNU方言。
c++98         // ISO C++1998标准及其修订案(2003)。
gnu++98       // “-std=c++98”的GNU方言,这是C++的默认选项。
c++0x         // 即将到来的ISO C++0x标准的工作草案。(译者注:2011年ISOC++标准委员会已经公布了C++11标准)。
gnu++0x       // “-std=c++0x”的GNU方言。

-aux-info headerfilename

把定义或声明在一个翻译单元(即C语言源代码文件)中的所有函数的原型声明输出到headerfilename文件中。该选项仅用于C语言。

-traditional

兼容最原始的C语法检查。

(3) 调试选项

-g

根据操作系统本地的格式产生调试信息到可执行文件中。GDB会使用这些信息。

-ggdb

同“-g”,但如果有可能,会包含一些GDB扩展。

-gcoff

同“-g”,但如果有可能,会产生COFF格式的信息。

-gxcoff

同“-g”,但如果有可能,会产生XCOFF格式的信息。

-gxcoff++

同“-gxcoff”,但会使用仅被GDB所理解的GNU扩展。

-gvms

同“-g”,但如果有可能,会产生VMS格式的信息。

-glevel

-ggdblevel

-gstabslevel

-gcofflevel

-gxcofflevel

-gvmslevel

要求调试信息,但也使用用来指定有多少调试信息的level等级。默认等级为2,有效的level值有0、1、2、3。
其中,0表示无调试信息,1表示最小的调试信息。

-gtoggle

关闭调试信息的产生,否则默认产生“-g2”。

(4) 预处理选项

-D name

预定义name为一个宏,默认定义为1。

-D name=definition

预定义name为一个宏,真值为definition。
注:“-D”和“-U”的处理在“-imacros file”和“include file”之前。

-U name

取消名为name的宏定义。

-undef

不预定义任何由系统指定或GCC指定的宏。

-I dir

增加目录dir到头文件的搜索目录,并且dir优先于标准头文件库搜索。

-include file

如同源文件中的“#include “file””指令。

-imacros file

如同“-include file”选项,但file中被认为是宏定义。

(5) 链接选项

-llibrary

-l library

当链接时搜索名为library的库。(注:第二个形式仅仅为POSIX相容。)

-s

从可执行文件中移除所有的符号表和重定位信息。

-static

在支持动态链接的系统上,该选项阻止编译器链接到动态共享库。在其他系统上,该选项无效。

-shared

产生一个能被链接到其它目标文件以产生一个可执行文件的共享库。注:并不是所有的系统都支持该选项。

-nostrtfiles

当链接时,不使用标准的系统启动文件,而标准系统库文件仍然能够正常被使用,
除非指定“-nostdlib”或“-nodefaultlibs”选项。

-nodefaultlibs

当链接时,不使用标准的系统库文件,而只有指定的库文件才被传递给链接器;
但是,标准的启动文件仍可正常使用,除非指定了“-nostartfiles”选项。

-nostdlib

等同于同时使用“-nostartfiles”和“-nodefaultlibs”两个选项,
即当链接时,不使用标准的系统启动文件和库文件,只有指定的启动文件和库文件才被传递给链接器。

-Wl,option

把option作为一个选项传递给链接器。如果option中包含逗号,它就在逗号处分隔多个选项
(译者注:即如果option中需要包含多个选项,可以用逗号隔开,而一次性传递,这在传递带参数的选项时非常有用)。
对于GNU链接器,可以把option中的逗号换成等号。
说明:W后面的l是字母L的小写,不是数字1。

-Xlinker option

把option作为一个选项传递给链接器。使用这个选项可以提供GCC无法识别的、待定系统上的链接选项。
如果传递一个带有参数的选项,必须使用两次“-Xlinker”,一次用于传递选项,另一次用于传递参数。
对于GNU链接器,可以使用“option=value”来传递带参数的选项,而不是使用两次“-Xlinker”。

-u symbol

假装取消symbol的符号定义,以强迫库模块间的链接去定义它。
可以多次使用带不同symbol的-u选项,以强迫额外的库模块的加载。

-T script

把script作为链接脚本来使用,该选项被大部分使用GNU链接器的系统所支持。

(6) 目录搜索选项

-Idir

添加目录dir到头文件搜索目录,并且dir优先于系统头文件目录搜索,因此,dir中的头文件有可能被系统头文件覆盖。

-Ldir

添加目录dir到搜索“-l”的目录列表中(以搜索链接库)。

-Bprefix

指定在哪找到可执行文件、库文件、头文件(即include文件)、以及编译器自身的数据文件。

-iquotedir

添加目录dir到搜索头文件的目录列表中,它只适用于“#include “file””而不适用于“#include”;
除此之外,它和“-I”一样。

-I-

被废弃,现在使用“-iquotedir”。

(7) 警告选项

-w

关闭所有警告信息。

-Werror

把所有警告当作错误处理。

-pedantic

严格按照C/C++标准来提示警告信息。

-pedantic-errors

像“-pedantic”,但只产生错误信息而提示警告信息。

-Wall

打开所有的警告信息。

(8) 优化选项

-O0

不优化。减少编译时间以及让调试产生所期望的结果。这是默认选项。

-O

-O1

对于大函数,该优化选项将占用较长的时间和相当大的内存。
使用该选项,编译器试图减少目标码的大小和执行时间,而不会执行任何将花费大量编译时间的优化。
说明:O后面的1是数字1,而不是字母L的小写。

-O2

除了-O指定的所有优化选项外,该选项还进行更多的优化。
除了涉及空间和速度交换的优化选项外,该选项将完成几乎所有的优化。
同-O选项相比,该选项虽然增加了编译时间,但也提高了生成代码的运行效果。

-O3

除了-O2指定的所有优化选项外,该选项还进行了更多的优化。

-Os

  对代码尺寸大小的优化。该选项开启了-O2优化选项中不会增加代码尺寸大小的所有优化选项;
  同时,它还完成了一些旨在减少代码大小的进一步的优化。

-Ofast

忽略应严格顺从的标准。
它不但开启了一些-O3指定的所有优化选项,还开启了一些对于所有顺从标准的程序并不是都有效的优化。
如果指定了多个“-O”选项,无论带不带表示优化等级的数字,有效的总是最后一个。

5、C++和C的混合编程

(1) 在C++中调用C

在C++中调用C,就必须在C++文件中使用extern指令来声明 C函数 的头文件,而在C文件中则不需要这样的声明。一般使用extern “C”,其中的“C”并不是指C语言的意思,而是指某种规则,该规则命令为“C”。如:

/*  c++.cpp  */
extern “C” void csayhello(const char *str);

int main()
{
    csayhello(“Hello from C++to C”);
    return 0;
}
/*  csayhello.c  */
#include 

void csayhello(const char *str)
{
    printf(%s\n”, str);
}

编译上面两个文件:

$ g++ -c c++.cpp -o c++.o
$ gcc -c csayhello.c -o csayhello.o
$ g++ c++.o csayhello.o -o program

(2) 在C中调用C++

C++程序有责任为C程序提供C语言形式的接口。如:

/*  c++sayhello.cpp  */
#include 

extern “C” void c++sayhello(const char *str);

void c++sayhello(const char *str)
{
    std::cout << str << endl;
}
/*  c.c  */
void c++sayhello(const char *str);

int main(void)
{
    c++sayhello(“Hello from C to C++);
    return 0;
}

编译上面两个文件:

$ g++ -c c++sayhello.cpp -o c++sayhello.o
$ gcc -c c.c -o c.o
$ gcc c++sayhello.o c.o -o program

6、共享库

(1) 共享库版本和命名规则

共享库的每个不兼容版本是通过一个唯一的主要版本标识符来区分的,这个主要版本标识符是共享库的真实名称的一部分。根据惯例,主要版本标识符由一个数字构成,这个数字随着库的每个不兼容版本的发布而顺序递增。除了主要版本标识符之外,真实名称还包一个次要版本标识符,它用来区分库的主要版本中兼容的次要版本。真实名称的格式规范为 libname.so.major-id.minor-id

与主要版本标识符一样,次要版本标识符可以是任意字符串。但根据惯例,它要么是一个数字,要么是两个由点分隔的数字,其中第一个数字标识出了次要版本,第二个数字表示该次要版本中的补丁号或修订号。

共享库的soname包括相应的真实名称中的主要版本标识符,但不包含次要版本标识符。因此,soname的形式为 libname.so.major-id

通常会将 soname 创建为包含真实名称的目录中的一个相对符号链接。

除了真实名称和soname之外,通常还会为每个共享库定义第三个名称:链接器名称————将可执行文件与共享库链接起来时会用到这个名称。链接器名称是一个只包含库名同时不包含主要或次要版本标识符的符号链接,因此其形式为 libname.so

(2) 兼容与不兼容库比较

如果是兼容的话则意味着只需要修改库的真实名称的次要版本标识符即可,如果是不兼容的话则意味着必须要定义一个库的新主要版本。

当满足下列条件时表示修改过的库与既有库版本兼容:

A. 库中所有公共方法和变量的语义保持不变。
   换句话说,每个函数的参数列表不变并且对全局变量和返回参数产生的影响不变,同时返回同样的结果值。
B. 没有删除库的公共API中的函数和变量,但向公共API中添加新函数和变量不会影响兼容性。
C. 在每个函数中分配的结构以及每个函数返回的结构保持不变。类似的,由库导出的公共结构保持不变。
   这个规则的一个例外情况是在特定情况下,可能会向既有结构的结尾处添加新的字段,
   但当调用程序在分配这个结构类型的数组时会产生问题。

如果所有这些条件都得到了满足,那么在更新新库名时就只需要调整既有名称中的次要版本号,否则就需要创建库的一个新主要版本。

(3) 动态加载库

dlopen API 使得程序能够在运行时打开一个共享库,根据名字在库中搜索一个函数,然后调用这个函数。
在运行时采用这种方式加载的共享库通常被称为动态加载的库,它的创建方式与其他共享库的创建方式完全一样。

核心dlopen API由下列函数构成:

A. dlopen函数打开一个共享库,返回一个供后续调用使用的句柄;
B. dlsym函数在库中搜索一个符号(一个包含函数或变量的字符串)并返回其地址;
C. dlclose函数关闭之前由dlopen打开的库;
D. dlerror函数返回一个错误消息字符串,在调用上述函数中的某个函数发生错误时可以使用这个函数来获取错误消息。

要在Linux上使用 dlopen API 构建程序必须要指定 -ldl 选项以便与 libdl 库链接起来 。

如果 dlopen 会自动加载被依赖的库;如果有必要的话,这一过程会递归进行。

在同一个库文件中可以多次调用 dlopen 函数,但将库加载进内存的操作只会发生一次(第一次调用),所有的调用都返回同样的句柄值。

(4) 初始化和终止函数

可以定义一个或多个在共享库被加载和卸载时自动执行的函数,这样在使用共享库时就能够完成一些初始化和终止工作了。不管库是自动被加载还使用 dlopen 接口加载的,初始化函数和终止函数都会被执行。

初始化和终止函数是使用 GCCconstructordestructor 特性来定义折。在库被加载时需要执行的所有函数都应该定义成下面的形式:

void __attribute__((constructor)) some_name_load(void)
{
    /* Initialization code */
}

void __attribute__((destructor)) some_name_load(void)
{
    /* Finalization code */
}

你可能感兴趣的:(Linux)