0.前言

在学习如何制作静态库和共享库之前,我们来了解GCC编译器的基本工作流程和GCC常用参数的使用。

1.GCC基本工作流程

现在假设有一个helloworld.c源程序,功能是只打印HelloWorld

  1. 将C源程序进行预处理(预处理一般做的工作是将宏替换和头文件展开 ):gcc -E helloworld.c > helloworld.i,注意>与>>的区别.这里呢,helloworld.i文件只是存储gcc -E helloworld.c的结果。
  2. 将helloworld.i文件生成汇编代码:gcc -S helloworld.i,这条命令会生成后缀名.s的文件,里面是一些汇编指令。
  3. 将存放汇编指令的文件编译成二进制文件:gcc -c helloworld.s,这条命令会生成后缀名为.o的二进制文件.
  4. gcc helloworld.o将二进制文件默认生成a.out文件;如果需要指定生成的目标文件名则:gcc helloworld.o -o app
  5. ./a.out即可运行。上面的基本流程我们可以用下图表示:
    gcc编译基本流程.png

2.gcc编译器的常用参数

hello.c如下

#include <stdio.h>
#include <head.h>

int returnMess(int val)
{
        return val;
}
int main()
{
#ifdef DEBUG
        printf("HelloWorld");
#endif
        printf("%d",returnMess(100));
        return 0;
}

  1. -I:指示待编译的头文件的路径。比如说main主函数所在的源程序中包含了其他自定义的头文件,编译的时候为了将它包含进来,可以用-I选项指示自定义的头文件所在的路径。
##hello.c中包含了自定义的头文件head.h,这个文件在/home/xiaocer/练习/include下
##这样生成a.out可执行文件
[xiaocer@localhost practice]gcc hello.c -I ../include
  1. -O:优化选项,其值有0~3。默认的编译选项为O0,表示不做任何优化。
##对hello.c文件采用二级优化来编译
[xiaocer@localhost practice]$ gcc hello.c -I ../include -D DEBUG -o result -O2
  1. -D:指定宏编译。指定宏编译如下:
[xiaocer@localhost practice]gcc hello.c -I ../include -D DEBUG  -o result
  1. -L:指定包含的库路径
  2. -l:指定包含的库名,Linux下库名一般是libxxx.so(动态库)或者libxxx.a(静态库),指定库名的时候只要-l xxx
  3. -g:在最终编译生成的可执行文件中加入调试信息有利于调试
  4. -Wall:显示编译时期所有的警告信息
[xiaocer@localhost practice]$ gcc hello.c -I ../include -o result.exe -Wall
  1. -lstdc++:用GCC编译器编译C++写的源程序。比如说
[xiaocer@localhost practice]$ gcc hello.cpp -lstdc++
  1. -o:指定编译生成的目标可执行文件的文件名,如果不指定则默认生成a.out文件。
  2. -c:将源文件编译成.o为后缀名的二进制文件
##将hello.c编译成二进制文件
[xiaocer@localhost practice]$ gcc hello.c -c -I ../include
  1. -E:将源文件输出到标准输出设备默认为Linux终端,输出前进行宏替换和头文件展开。
  2. -S:将编译成.s为后缀名结尾的汇编文件。

3.静态库的制作

  1. Linux系统下静态库名一般是libxxx.a,而windows下呢是以后缀名为.lib结尾的。
  2. 制作步骤:
    1. 将需要制作成静态库的源文件编译成以.o为后缀名的二进制文件
    2. 使用ar rcs libxxx.a yyy.o yyy1.o yyy2.o命令将二进制文件打包。ar:archived意为归档的。
  3. 示例:
    1. 现在有add.c这个实现好的源文件,里面是实现两个整数的相加算法的一个函数add()。现在提供给客户使用add这个函数提供的功能,但是又不想提供add.c这个源文件给客户暴露add函数具体的实现。所以呢,可以将add.c 制作成静态库libadd.a,然后同add函数的声明所在的头文件add.h提供给客户即可。
    ##现在在/home/xiaocer/练习/include目录下有add.h和add.c两个文件
    ##将add.c源文件编译成add.o这个二进制文件
    [xiaocer@localhost include]$ gcc add.c -c -I ./
    ##将二进制文件打包成静态库,这样就会生成一个libadd.a这个文件
    [xiaocer@localhost include]$ ar rcs libadd.a add.o
    
    1. 静态库制作好后现在提供给客户使用。客户拿到libadd.a文件后,在hello.c文件中使用add这个函数功能。hello.c文件内容如下:
    #include <stdio.h>
    #include <head.h>
    #include <add.h>
    
    int returnMess(int val)
    {
            return val;
    }
    int main()
    {
            int a;
    #ifdef DEBUG
            printf("HelloWorld");
    #endif
            printf("%d\n",returnMess(100));
            printf("%d\n",add(50,50));
            return 0;
    }
    
    
    1. 然后呢我们根据之前学的gcc编译命令对hello.c进行编译如下:
    ##静态库在/home/xiaocer/练习/include下,hello.c在/home/xiaocer/练习/practice下
    ##静态库名为add
    [xiaocer@localhost practice]$ gcc hello.c -I ../include -o result.exe -L ../include -l add
    ##这样可以编译成result.exe可执行文件
    [xiaocer@localhost practice]$ ./result.exe
    ##运行成功表示制作静态库成功
    

4.动态库的制作

  1. Linux系统下动态库名一般是libxxx.so,而Windows下的动态库文件的后缀名是.dll.
  2. 制作步骤:
    1. 将需要制作成动态库的源文件编译成二进制文件,编译的时候需要加上选项-fPIC(position-independent code),这样的话编译成与位置无关的代码。
    2. 将后缀名为.o的二进制文件打包,打包时使用-share选项
  3. 示例:还是以之前的add.c为例:
    1. 共享库的制作
    ##将add.c文件编译成与位置无关的代码
    [xiaocer@localhost include]$ gcc add.c -c -fPIC -I ./
    ##将二进制文件打包成libadd.so动态库
    [xiaocer@localhost include]$ gcc add.o -shared -o libadd.so
    
    1. 那么客户如何使用这个共享库呢?像下面这样:gcc -o result.exe main.c -L ../include -l add [xiaocer@localhost practice]$ ./result.exe
      1. 会报错:./result.exe: error while loading shared libraries: libadd.so: cannot open shared object file: No such file or directory这样直接运行是不可行的,因为可执行文件在运行时系统找不到libadd.so这个共享库的路径。我们来查看result.exe会使用到的共享库
        [xiaocer@localhost practice]$ ldd result.exe
        
      2. 然后发现 libadd.so => not found
    2. 所以我们还要介绍如何让可执行文件运行时能够加载自定义的共享库成功,这里有很多种方法。方法如下:
      1. 方法1:将共享库路径添加到环境变量LD_LIBRARY_PATH中
      ##我们来先查看该环境变量的值,发现啥都没有
      [xiaocer@localhost include]$ echo $LD_LIBRARY_PATH
      ##设置该环境变量的值。为了后面设置的该环境变量的值不会影响原来的值,需要加上:$LD_LIBRARY_PATH
      [xiaocer@localhost include]$ export LD_LIBRARY_PATH=/home/xiaocer/练习/include/:$LD_LIBRARY_PATH
      
      ##这时ldd result.exe一下显示libadd.so found
      ##再运行result.exe成功
      [xiaocer@localhost include]$ ./result.exe
      
      1. 方法2.配置一下连接器相关的配置文件(/etc/ld.so.conf文件)
      ##打开/etc/ld.so.conf这个文件
      [xiaocer@localhost include]$sudo vim /etc/ld.so.conf
      ##然后将需要加载的共享库的路径添加进去
      ##然后运行下面这条命令使的编辑生效
      [xiaocer@localhost include]$ sudo ldconfig -v
      ##这是再ldd result.exe发现成功
      ##运行可执行文件成功
      

5.动态链接库和静态链接库的各自特点

1.动态链接库有利于进程间资源共享。
  1. 对于动态链接库,当某个程序在运行中要调用某个动态链接库中的函数时,OS首先会查看所有正在运行着的程序,查看内存中是否已有此库函数的拷贝。如果有则让其共享那一个拷贝;没有时才链接载入。这样可以节省系统的内存资源
  2. 而对于静态链接库,如果系统中多个程序都要调用某个静态链接库中的函数时,则每个程序都要将这个库函数拷贝到自己的代码段中,这显然将占用更大的内存资源。
2.动态链接库将一些程序升级变得更简单
  1. 使用静态链接库的话,如果库发生变化,使用库的程序需要重新编译。因为在链接阶段,目标文件和库一起链接形成最终的可执行文件,如果库发生变化,需要重新链接一次。
  2. 使用动态库,只要动态库提供给程序的接口名称没变,只要使用重新编译生成的动态库替换原来的就可以了。
3.使用静态链接库的程序执行更快
  1. 静态链接库在编译链接的时候,就将库函数装载到程序中去了。而动态库函数必须在运行的时候才被装载。