• GCC基本知识


    掌握下面的对GCC会有一个比较清晰的大致的了解:

    不经意间,GCC已发展到了4.3的版本,尽管在软件开发社区之外乏人闻问,但因为GCC在几乎所有开源软件和自由软件中都会用到,因此它的编译性能的涨落会直接影响到Linux 、Firefox 乃至于OpenOffice.org和Apache等几千个项目的开发。因此,把GCC摆在开源软件的核心地位是一点也不为过。另一方面,GCC4.3的出现,正在牵引着广大程序员们的心。如果我们非要用一个词来说明GCC与程序员之间的关系,那无疑是"心随心动"。


    历史篇
    作为自由软件的旗舰项目,Richard Stallman 在十多年前刚开始写作 GCC 的时候,还只是把它当作仅仅一个 C 程序语言的编译器;GCC 的意思也只是 GNU C Compiler 而已。经过了这么多年 的发展,GCC 已经不仅仅能支持 C 语言;它现在还支持Ada 语言、C++ 语言、Java 语言、Objective C 语言、Pascal 语言、COBOL语言,以及支持函数式编程和逻辑编程的 Mercury 语言,等等。而 GCC 也不再单只是 GNU C 语言编译器的意思了,而是变成了 GNU Compiler Collection 也即是 GNU 编译器家族的意思了。另一方面,说到 GCC 对于各种硬件平台的支持,概括起来就是一句话:无所不在。几乎所有有点实际用途的硬件平台,甚至包括有些不那么有实际用途的硬件平台。


    Gcc 简介
    Linux系统下的gcc(GNU C Compiler)是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作品之一。Gcc是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%。
    官方网站:http://gcc.gnu.org/

           gcc是linux的唯一编译器,没有gcc就没有linux,gcc的重要性就不可言喻啦。居然这么重要,那就很值得我们来好好研究下啦。好啦,开始我们的gcc之旅吧!


    首先消除gcc和g++误区吧。
    gcc和g++都是GNU(组织)的一个编译器。

    误区一:gcc只能编译c代码,g++只能编译c++代码

    两者都可以,但是请注意:
    1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的,例如:
    #include <stdio.h>
    int main(int argc, char* argv[]) {
       if(argv == 0) return;
       printString(argv);
       return;
    }
    int printString(char* string) {
    sprintf(string, "This is a test."n");
    }
    如果按照C的语法规则,OK,没问题,但是,一旦把后缀改为cpp,立刻报三个错:“printString未定义”;
    “cannot convert `char**' to `char*”;
    ”return-statement with no value“;
    分别对应前面红色标注的部分。可见C++的语法规则更加严谨一些。


    2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

    误区二:gcc不会定义__cplusplus宏,而g++会

    实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

    误区三:编译只能用gcc,链接只能用g++

    严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。

    误区四:extern "C"与gcc/g++有关系

    实际上并无关系,无论是gcc还是g++,用extern "c"时,都是以C的命名方式来为symbol命名,否则,都以c++方式命名。试验如下:
    me.h:
    extern "C" void CppPrintf(void);

    me.cpp:
    #include <iostream>
    #include "me.h"
    using namespace std;
    void CppPrintf(void)
    {
         cout << "Hello"n";
    }

    test.cpp:
    #include <stdlib.h>
    #include <stdio.h>
    #include "me.h"        
    int main(void)
    {
        CppPrintf();
        return 0;
    }

    1. 先给me.h加上extern "C",看用gcc和g++命名有什么不同

    [root@root G++]# g++ -S me.cpp
    [root@root G++]# less me.s
    .globl _Z9CppPrintfv        //注意此函数的命名
            .type   CppPrintf, @function
    [root@root GCC]# gcc -S me.cpp
    [root@root GCC]# less me.s
    .globl _Z9CppPrintfv        //注意此函数的命名
            .type   CppPrintf, @function
    完全相同!
                   
    2. 去掉me.h中extern "C",看用gcc和g++命名有什么不同

    [root@root GCC]# gcc -S me.cpp
    [root@root GCC]# less me.s
    .globl _Z9CppPrintfv        //注意此函数的命名
            .type   _Z9CppPrintfv, @function
    [root@root G++]# g++ -S me.cpp
    [root@root G++]# less me.s
    .globl _Z9CppPrintfv        //注意此函数的命名
            .type   _Z9CppPrintfv, @function
    完全相同!
    【结论】完全相同,可见extern "C"与采用gcc/g++并无关系,以上的试验还间接的印证了前面的说法:在编译阶段,g++是调用gcc的。

       后面还会继续啦 (*^__^*) 嘻嘻……

    -----自己也是刚接触gcc,也是在不断学习,不断的在网上搜些资料不断的进行总结什么的,难免有些错误的地方,还望大家给指正。

           今天,我们继续gcc之旅吧。上节我们讲了些gcc的历史发展什么的,还有就是gcc与g++的区别。今天我们就从整体上对gcc编译过程有个细致的了解,也好明白他的工作原理,好为以后深入学习研究打下个基础。


    gcc的编译流程分为四个步骤,分别为:
    · 预处理(Pre-Processing)
    · 编译(Compiling)
    · 汇编(Assembling)
    · 链接(Linking


    以hello.c为例子,在这四个步骤中可以设置选项分别生成hello.i, hello.s, hello.o以及最终的hello文件:
        hello.c : 最初的源代码文件;
        hello.i : 经过编译预处理的源代码;
        hello.s : 汇编处理后的汇编代码;
        hello.o : 编译后的目标文件,即含有最终编译出的机器码,但它里面所引用的其他文件中函数的内存位置尚未定义。
        hello / a.out(这是什么东西呢?搞错了吧) : 最终的可执行文件
        (还有.a(静态库文件), .so(动态库文件), .s(汇编源文件)留待以后讨论)

    下面就具体来查看一下gcc是如何完成四个步骤的。

    hello.c源代码

    #include<stdio.h>
    int main()
    {
       printf("Hello World!/n");
       return 0;
    }

    (1)预处理阶段

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

    《深入理解计算机系统》中是这么说的:
        预处理器(cpp)根据以字符#开头的命令(directives),修改原始的C程序。如hello.c中#include <stdio.h>指令告诉预处理器读系统头文件stdio.h的内容,并把它直接插入到程序文本中去。结果就得到另外一个C程序,通常是 以.i作为文件扩展名的。
       
    注意:
            Gcc指令的一般格式为:Gcc [选项] 要编译的文件 [选项] [目标文件]
    其中,目标文件可缺省,Gcc默认生成可执行的文件名为:编译文件.out

    [gan@localhost gcc]# gcc –E hello.c –o hello.i
    选项”-o”是指目标文件,”.i”文件为已经过预处理的C原始程序。以下列出了hello.i文件的部分内容:

    typedef int (*__gconv_trans_fct) (struct __gconv_step *,
             struct __gconv_step_data *, void *,
             __const unsigned char *,
             __const unsigned char **,
             __const unsigned char *, unsigned char **,
             size_t *);

    # 2 "hello.c" 2

    int main()
    {
    printf("Hello World!/n");
    return 0;
    }

    由此可见,gcc确实进行了预处理,它把”stdio.h”的内容插入到hello.i文件中。

    (2)编译阶段

    接下来进行的是编译阶段,在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成 汇编语言。用户可以使用”-S”选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。汇编语言是非常有用的,它为不同高级语言不同编译器提供了 通用的语言。如:C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。

    [gan@localhost gcc]# gcc –S hello.i –o hello.s
    以下列出了hello.s的内容,可见Gcc已经将其转化为汇编了,感兴趣的读者可以分析一下这一行简单的C语言小程序是如何用汇编代码实现的。
         .file   "hello.c"
         .section    .rodata
         .align 4

    .LC0:
         .string     "Hello World!"
         .text
    .globl main
         .type main, @function
    main:
         pushl %ebp
         movl %esp, %ebp
         subl $8, %esp
         andl $-16, %esp
         movl $0, %eax

         addl $15, %eax
         addl $15, %eax
         shrl $4, %eax
         sall $4, %eax
         subl %eax, %esp
         subl $12, %esp
         pushl $.LC0

         call puts
         addl $16, %esp
         movl $0, %eax
         leave
         ret
         .size   main, .-main
         .ident "GCC: (GNU) 4.0.0 20050519 (Red Hat 4.0.0-8)"
         .section    .note.GNU-stack,"",@progbits


    (3)汇编阶段

    汇编阶段是把编译阶段生成的”.s”文件转成目标文件,读者在此可使用选项”-c”就可看到汇编代码已转化为”.o”的二进制目标代码了。如下所示:
    [gan@localhost gcc]# gcc –c hello.s –o hello.o

    (4)链接阶段

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

    在这个源程序中并没有定义”printf”的函数实现,且在预编译中包含进的”stdio.h”中也只有该函数的声明,而没有定 义函数的实现,那么,是在哪里实现”printf”函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特 别指定时,gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf” 了,而这也就是链接的作用。

    函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成 的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在 程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时 默认使用动态库。
    (Linux下动态库文件的扩展名为".so"(Shared Object)。按照约定,所有动态库文件名的形式是libname.so(可能在名字中加入版本号)。这样,线程函数库被称作libthread.so。静态库的文件名形式是libname.a。共享archive的文件名形式是libname.sa。共享archive只是一种过渡形式,帮助人们从静态库转变到动态库。)
    完成了链接之后,gcc就可以生成可执行文件,如下所示。

    [gan@localhost gcc]# gcc hello.o –o hello

    运行该可执行文件,出现正确的结果如下。
    [root@localhost Gcc]# ./hello
    Hello World!

             好啦 这一节就到这。后面我们就开始对gcc的一些比较常用参数进行讲解啦。

    这一节,我们来关注下gcc的常用参数 ,有机会也好多加练习啦。                             

            首先来看看我们gcc的版本吧,gcc --version这个命令就会显示gcc的版本号啦 。好啦,下面开始下面开始讲解gcc的常用参数啦o(∩_∩)o...
    [参数详解]

    -c   
      只激活预处理,编译,和汇编,也就是他只把程序做成obj文件 
      例子用法: 
      gcc -c hello.c 
      他将生成.o的obj文件   
    -S 
      只激活预处理和编译,就是指把文件编译成为汇编代码。 
      例子用法 
      gcc -S hello.c 
      他将生成.s的汇编代码,你可以用文本编辑器察看 
    -E 
      只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里 
      面. 
      例子用法: 
      gcc -E hello.c > pianoapan.txt 
      gcc -E hello.c | more 
      慢慢看吧,一个hello word 也要与处理成800行的代码 
    -o 
      制定目标名称,缺省的时候,gcc 编译出来的文件是a.out,很难听,如果 你和我有同感,改掉它,哈哈 
      例子用法 
      gcc -o hello.exe hello.c (哦,windows用习惯了) 
      gcc -o hello.asm -S hello.c 
    -ansi 
      关闭gnu c中与ansi c不兼容的特性,激活ansi c的专有特性(包括禁止一 
      些asm inline typeof关键字,以及UNIX,vax等预处理宏,

    -Wall
          显示警告信息
    -O0 
    -O1 
    -O2 
    -O3 
      编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最 
      高   
       
    -g 
      只是编译器,在编译的时候,产生调试信息。

    -llibrary   
      制定编译的时候使用的库 
      例子用法 
      gcc -lcurses hello.c 
      使用ncurses库编译程序 
       
    -Ldir 
      制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然 
      编译器将只在标准库的目录找。这个dir就是目录的名称。    
    -gstabs 
      此选项以stabs格式声称调试信息,但是不包括gdb调试信息.   
    -gstabs+ 
      此选项以stabs格式声称调试信息,并且包含仅供gdb使用的额外调试信息. 
       
    -ggdb 
      此选项将尽可能的生成gdb的可以使用的调试信息. 
    -static   此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么 动态连接库,就可以运行.

    -share   此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.

    -traditional   试图让编译器支持传统的C语言特性

    -fno-asm 
      此选项实现ansi选项的功能的一部分,它禁止将asm,inline和typeof用作 
      关键字。 
         
    -fno-strict-prototype 
      只对g++起作用,使用这个选项,g++将对不带参数的函数,都认为是没有显式 
      的对参数的个数和类型说明,而不是没有参数. 
      而gcc无论是否使用这个参数,都将对没有带参数的函数,认为城没有显式说 
      明的类型 
       
    -fthis-is-varialble 
      就是向传统c++看齐,可以使用this当一般变量使用. 
       
    -fcond-mismatch 
      允许条件表达式的第二和第三参数类型不匹配,表达式的值将为void类型 
       
    -funsigned-char 
    -fno-signed-char 
    -fsigned-char 
    -fno-unsigned-char 
      这四个参数是对char类型进行设置,决定将char类型设置成unsigned char(前 
      两个参数)或者 signed char(后两个参数)

    -include file 
      包含某个代码,简单来说,就是便以某个文件,需要另一个文件的时候,就可以 
      用它设定,功能就相当于在代码中使用#include 
      例子用法: 
      gcc hello.c -include /root/pianopan.h 
       
    -imacros file 
      将file文件的宏,扩展到gcc/g++的输入文件,宏定义本身并不出现在输入文件 
      中 
       
    -Dmacro 
      相当于C语言中的#define macro 
       
    -Dmacro=defn 
      相当于C语言中的#define macro=defn 
       
    -Umacro 
      相当于C语言中的#undef macro

    -undef 
      取消对任何非标准宏的定义 
       
    -Idir 
      在你是用#include"file"的时候,gcc/g++会先在当前目录查找你所制定的头 
      文件,如果没有找到,他回到缺省的头文件目录找,如果使用-I制定了目录,他 
      回先在你所制定的目录查找,然后再按常规的顺序去找. 
      对于#include,gcc/g++会到-I制定的目录查找,查找不到,然后将到系 
      统的缺省的头文件目录查找 
       
    -I- 
      就是取消前一个参数的功能,所以一般在-Idir之后使用 
       
    -idirafter dir 
      在-I的目录里面查找失败,讲到这个目录里面查找. 
       
    -iprefix prefix 
    -iwithprefix dir 
      一般一起使用,当-I的目录查找失败,会到prefix+dir下查找 
       
    -nostdinc 
      使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确 
      限定头文件的位置 
       
    -nostdin C++ 
      规定不在g++指定的标准路经中搜索,但仍在其他路径中搜索,.此选项在创建 
      libg++库使用 
       
    -C 
      在预处理的时候,不删除注释信息,一般和-E使用,有时候分析程序,用这个很 
      方便的   
    -M 
      生成文件关联的信息。包含目标文件所依赖的所有源代码 
      你可以用gcc -M hello.c来测试一下,很简单。   
    -MM 
      和上面的那个一样,但是它将忽略由#include造成的依赖关系。   
    -MD 
      和-M相同,但是输出将导入到.d的文件里面 
       
    -MMD 
      和-MM相同,但是输出将导入到.d的文件里面 
       
    -Wa,option 
      此选项传递option给汇编程序;如果option中间有逗号,就将option分成多个选 
      项,然后传递给会汇编程序 
       
    -Wl.option 
      此选项传递option给连接程序;如果option中间有逗号,就将option分成多个选 项,然后传递给会连接程序.

    -x language filename 设定文件所使用的语言,使后缀名无效,对以后的多个有效.也就是根据约定C语言的后缀名称是.c的,而C++的后缀名是.C或者.cpp,如果你很个性,决定你的C代码文件的后缀名是.pig 哈哈,那你就要用这个参数,这个参数对他后面的文件名都起作用,除非到了下一个参数的使用。 可以使用的参数吗有下面的这些   `c', `objective-c', `c-header', `c++', `cpp-output', `assembler', and `assembler-with-cpp'.   看到英文,应该可以理解的。   例子用法:   gcc -x c hello.pig

    -x none filename   关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型   例子用法:   gcc -x c hello.pig -x none hello2.c

    -pipe   使用管道代替编译中临时文件,在使用非gnu汇编工具的时候,可能有些问题   gcc -pipe -o hello.exe hello.c

    -funsigned-char -fno-signed-char -fsigned-char -fno-unsigned-char   这四个参数是对char类型进行设置,决定将char类型设置成unsigned char(前两个参 数)或者 signed char(后两个参数)

              GCC的参数是很多的,这也只是部分。不过对于初学者来说,一时之间掌握这么多的参数也是有点困难的,不过一般推荐使用命令:gcc    -Wall hello.c   -o hello 就足够啦。一般要使用-Wall这个参数,他可以列出源程序在编译过程中出现的错误警告等信息,这是很有帮助的,注意:-Wall这个参数是在编译过程中使用的,若先把源程序编译成目标文件,则在链接过程中不要使用这个参数。你还可以再加个 -v参数:列出比较详细的信息,(在标准错误)显示执行编译阶段的命令.同时显示编译器驱动程序,预处理器,编译器的版本号.
           这些在我们平时使用的过程中应该已经足够啦,若还想了解其他的一些参数,可以下载gcc的帮助文档有时间自己研究下。

    ------呀,好像都几天没有写些技术方面的总结性文章啦,看来今后自己要更加努力些才行啊。这一节主要是些杂乱性的知识点,内容也比较少(呵呵,自己好像在偷懒喔~~~)

    多文件编译、连接

    如果原文件分布于多个文件中:file1.c, file2,c
        $ gcc -Wall file1.c file2.c -o name

    若对其中一个文件作了修改,则可只重新编译该文件,再连接所有文件:
        $ gcc -Wall -c file2.c
        $ gcc file1.c file2.o -c name
    注意:有些编译器对命令行中的.o文件的出现顺序有限制:含有某函数定义的文件必须出现在含有调用该函数的文件之后。好在GCC无此限制。
    编译预处理

    在程序中包含与连接库对应的头文件是很重要的方面,要使用库,就一定要能正确地引用头文件。一般在代码中通过#include引入头文件, 如果头文件位于系统默认的包含路径(/usr/includes), 则只需在#include中给出头文件的名字, 不需指定完整路径. 但若要包含的头文件位于系统默认包含路径之外, 则有其它的工作要做: 可以(在源文件中)同时指定头文件的全路径. 但考虑到可移植性,最好通过-I在调用gcc的编译命令中指定。
    下面看这个求立方的小程序(阴影语句表示刚开始不存在):
    #include <stdio.h>
    int main(int argc, char *argv[])
    {
    double x = pow (2.0, 3.0);
    printf("The cube of 2.0 is %f/n", x);
    return 0;

    使用gcc-2.95来编译它(-lm选项在后面的连接选项中有介绍, 这里只讨论头文件的包含问题):
    $ gcc-2.95 -Wall pow.c -lm -o pow_2.95
    pow.c: In function `main':
    pow.c:5: warning: implicit declaration of function `pow'

    程序编译成功,但gcc给出警告: pow函数隐式声明。
    $ ./pow_2.95
    The cube of 2.0 is 1.000000

    明显执行结果是错误的,在源程序中引入头文件(#include <math.h>),消除了错误。

    关键:不要忽略Warning信息!它可能预示着,程序虽然编译成功,但运行结果可能有错。故,起码加上"-Wall"编译选项!并尽量修正Warning警告。
    不过现在比较新的gcc在编译源程序没有引入头文件时,也会出现如下代码:
    test1.c: In function 'main':
    test1.c:4: warning: implicit declaration of function 'pow'
    test1.c:4: warning: incompatible implicit declaration of built-in function 'pow'
    程序编译成功,执行结果也是正确的。因为现在比较新的编译器会自动的引入标准库中的头文件。

    搜索路径
    首先要理解 #include<file.h>和#include"file.h"的区别:
    #include<file.h>只在默认的系统包含路径搜索头文件
    #include"file.h"首先在当前目录搜索头文件, 若头文件不位于当前目录, 则到系统默认的包含路径搜索头文件

    UNIX类系统默认的系统路径为:
    头文件,包含路径: /usr/local/include/ or /usr/include/
    库文件,连接路径: /usr/local/lib/          or /usr/lib/    
    对于标准c库(glibc或其它c库)的头文件, 我们可以直接在源文件中使用#include <file.h>来引入头文件.

    相关的4篇文章均转自下列部落格:

    http://hi.baidu.com/god_86/blog/item/8bb469172dbdd04c20a4e992.html

              ------今天就来讲讲有关函数库方面的些知识吧

    与外部库连接
    --------------------------------------------------------------------------------
    前面介绍了如何包含头文件. 而头文件和库是息息相关的, 使用库时, 要在源代码中包含适当的头文件,这样才能声明库中函数的原型(发布库时, 就需要给出相应的头文件).

    和包含路径一样, 系统也有默认的连接路径:
    头文件,包含路径: /usr/local/include/ or /usr/include/
    库文件,连接路径: /usr/local/lib/          or /usr/lib/  

    同样地, 我们想要使用某个库里的函数, 必须使用那些将这个库连接到函数的程序中.

    有一个例外: libc.a或libc.so (C标准库,它包含了ANSI C所定义的C函数)是不需要你显式连接的, 所有的C程序在运行时都会自动加载c标准库. 

    除了C标准库之外的库称之为"外部库", 它可能是别人提供给你的, 也可能是你自己创建的(后面有介绍如何创建库的内容).

    外部库有两种:(1)静态连接库lib.a
                               (2)共享连接库lib.so 
    两者的共同点:
        .a, .so都是.o目标文件的集合,这些目标文件中含有一些函数的定义(机器码),而这些函数将在连接时会被最终的可执行文件用到。

    两者的区别:
        静态库.a : 当程序与静态库连接时,库中目标文件所含的所有将被程序使用的函数的机器码被copy到最终的可执行文件中. 静态库有个缺点: 占用磁盘和内存空间. 静态库会被添加到和它连接的每个程序中, 而且这些程序运行时, 都会被加载到内存中. 无形中又多消耗了更多的内存空间.


        共享库.so : 与共享库连接的可执行文件只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中(因此,共享库稍微比静态库慢些,但这个影响可以忽略不计), 这样就使可执行文件比较小, 节省磁盘空间(更进一步,操作系统使用虚拟内存,使得一份共享库驻留在内存中被多个程序使用).共享库还有个优点: 若库本身被更新, 不需要重新编译与它连接的源程序(注意:库提供的接口应该不变,比如库中的函数个数不要变,只是把这些函数功能增强啦)。(windows系统中共享库的后缀名是 .dll (dynamic linking library的缩写))。以为共享库有这么多的优点,在大部分的OS上,如果可能的话,gcc都会优先使用共享库。
    使用静态库的可执行文件,只要这一个文件掉入内存就可以运行啦。
    若可执行文件使用了共享库,可执行文件运行时,它的加载器必须能发现共享库并把它加载到内存中。默认的搜索系统目录设置为:/usr/local/lib and /usr/lib. 环境变量LD_LIBRARY_PATH可以用来设置搜索目录的列表。

    静态库

    下面我们来看一个简单的例子,计算2.0的平方根(假设文件名为sqrt.c):

    #include <math.h>
    #include <stdio.h>
    int
    main (void)
    {
    double x = sqrt (2.0);
    printf ("The square root of 2.0 is %f/n", x);
    return 0;
    }

    用gcc将它编译为可执行文件:
    $ gcc -Wall sqrt.c -o sqrt
    编译成功,没有任何警告或错误信息。执行结果也正确。
    $ ./sqrt
    The square root of 2.0 is 1.414214
        
    下面我们来看看刚才使用的gcc版本:
    $ gcc --version
    gcc (GCC) 4.0.2 20050808 (prerelease) (Ubuntu 4.0.1-4ubuntu9)

    现在我用2.95版的gcc把sqrt.c再编译一次:
    $ gcc-2.95 -Wall sqrt.c -o sqrt_2.95
    /tmp/ccVBJd2H.o: In function `main':
    sqrt.c:(.text+0x16): undefined reference to `sqrt'
         collect2: ld returned 1 exit status
        
    编译器会给出上述错误信息,这是因为sqrt函数不能与外部数学库"libm.a"相连。sqrt函数没有在程序中定义,也不存在于默认C库 "libc.a"中,如果用gcc-2.95,应该显式地选择连接库。上述出错信息中的"/tmp/ccVBJd2H.o"是gcc创造的临时目标文件,用作连接时用。

    使用下列的命令可以成功编译:
    $ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95
    它告知gcc:在编译sqrt.c时,加入位于/usr/lib中的libm.a库(C数学库)。

    C库文件默认位于/usr/lib, /usr/local/lib系统目录中; gcc默认地从/usr/local/lib, /usr/lib中搜索库文件。(在我的Ubuntu系统中,C库文件位于/urs/lib中)。

    这里还要注意连接顺序的问题,比如上述命令,如果我改成:
    $ gcc-2.95 -Wall /usr/lib/libm.a sqrt.c -o sqrt_2.95
    gcc会给出出错信息:
    /tmp/cc6b3bIa.o: In function `main':
    sqrt.c:(.text+0x16): undefined reference to `sqrt'
    collect2: ld returned 1 exit status

    正如读取目标文件的顺序,gcc也在命令行中从左向右读取库文件——任何包含某函数定义的库文件必须位于调用该函数的目标文件之后!

    指定库文件的绝对路径比较繁琐,有一种简化方法,相对于上述命令,可以用下面的命令来替代:
    $ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95
    其中的"-l"表示与库文件连接,"m"代表"libm.a"中的m。一般而言,"-lNAME"选项会使gcc将目标文件与名为"libNAME.a"的库文件相连。(这里假设使用默认目录中的库,对于其他目录中的库文件,参考后面的“搜索路径”。)


    上面所提到的"libm.a"就是静态库文件,所有静态库文件的扩展名都是.a!
    $ whereis libm.a
    libm: /usr/lib/libm.a /usr/lib/libm.so

    正如前面所说,默认的库文件位于/usr/lib/或/usr/local/lib/目录中。其中,libm.a是静态库文件,libm.so是后面会介绍的动态共享库文件。

    如果调用的函数都包含在libc.a中(C标准库被包含在/usr/lib/libc.a中,它包含了ANSI C所定义的C函数)。那么没有必要显式指定libc.a:所有的C程序运行时都自动包含了C标准库!(试试 $ gcc-2.95 -Wall hello.c -o hello)。

    共享库

    正因为共享库的优点,如果系统中存在.so库,gcc默认使用共享库(在/usr/lib/目录中,库文件以共享和静态两种版本存在)。

    运行:$ gcc -Wall -L. hello.c -lNAME -o hello
    gcc先检查是否有替代的libNAME.so库可用。   

    正如前面所说,共享库以.so为扩展名(so == shared object)。

    那么,如果不想用共享库,而只用静态库呢?可以加上 -static选项
    $ gcc -Wall -static hello.c -lNAME -o hello
    它等价于:
    $ gcc -Wall hello.c libNAME.a -o hello

    $ gcc-2.95 -Wall sqrt.c -static -lm -o sqrt_2.95_static
    $ gcc-2.95 -Wall sqrt.c -lm -o sqrt_2.95_default
    $ gcc-2.95 -Wall sqrt.c /usr/lib/libm.a -o sqrt_2.95_a
    $ gcc-2.95 -Wall sqrt.c /usr/lib/libm.so -o sqrt_2.95_so

    $ ls -l sqrt*
    -rwxr-xr-x 1 zp zp 21076 2006-04-25 14:52 sqrt_2.95_a
    -rwxr-xr-x 1 zp zp   7604 2006-04-25 14:52 sqrt_2.95_default
    -rwxr-xr-x 1 zp zp   7604 2006-04-25 14:52 sqrt_2.95_so
    -rwxr-xr-x 1 zp zp 487393 2006-04-25 14:52 sqrt_2.95_static

    上述用四种方式编译sqrt.c,并比较了可执行文件的大小。奇怪的是,-static -lm 和 /lib/libm.a为什么有区别?有知其原因着,恳请指明,在此谢谢了! :)

    如果libNAME.a在当前目录,应执行下面的命令:
    $ gcc -Wall -L. hello.c -lNAME -o hello
    -L.表示将当前目录加到连接路径。

  • 相关阅读:
    五一集训——图论
    Luogu P3942 将军令
    8.14 Round 1
    8.10 Round 1
    8.9 Round 1
    8.7 Round 2
    8.7 Round 1
    8.6 Round 1
    8.5 Round 2
    FHQ-Treap
  • 原文地址:https://www.cnblogs.com/biglucky/p/4034907.html
Copyright © 2020-2023  润新知