美文网首页
NDK(三):静态库和动态库

NDK(三):静态库和动态库

作者: 亦猿非猿 | 来源:发表于2018-12-19 07:10 被阅读88次

    计算机的发展,离不开前人的一点点积累,让我们可以直接使用别人的轮子进行快速开发。库存在的意义,就是避免重复造轮子,对于开发好的重复可用的代码,就直接封装为库。

    库一般分为两大类,一类是动态库,一类是静态库。

    静态库、动态库的介绍以及对比

    静态库

    静态库,一般命名后缀为.a
    会在编译阶段完全被整合进代码段中,所以生成的可执行性文件也比较大。
    优点是:编译后的执行程序不再需要函数库的支持,因为所有要使用的函数己经被编译进去了。

    这也是他的缺点:

    1. 生成可执行性文件体积大;
    2. 静态库如果发生了改变那么你的程序必须要重新编译。

    动态库

    动态库,一般命名后缀为.so
    动态库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。
    由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。

    gcc的编译流程

    在介绍怎样编译出静态库和动态库之前,先介绍一下,gcc的编译过程,怎样把一个c、c++文件编译为可执行文件。

    编译为可执行的文件,一般需要四个流程:

    1. 预处理(preprocessing)
    2. 编译(compilation)
    3. 汇编(assembly)
    4. 链接(linking)

    gcc编译参数

    # 常用选项
    -E:只进行预处理,不编译
    -S:只编译,不汇编,生成汇编文件
    -c:只编译、汇编,不链接,把汇编文件翻译为二进制机器指令文件
    -g:包含调试信息
    -I:指定include包含文件的搜索目录
    -o:输出成指定文件名
    # 高级选项
    -v:详细输出编译过程中所采用的每一个选项
    -C:预处理时保留注释信息
    

    注意:-o为输出指定文件名,这个命名不影响文件属性,如下面的预处理阶段,如果改为➜ gcc -E main.c -o main.o,也可能正常输出。但是,文件属性仍然只是进行预处理后的文件,而非可执行文件,不同后缀只是为了辨识不同阶段而已。

    预处理(preprocessing)

    先创建一个main.c文件

    #include <stdio.h>
    int main(){
        printf("hello world\n");
        return 0;
    }
    
    ➜  gcc -E main.c -o main.i
    

    预处理阶段主要处理include和define等。它把#include包含进来的.h 文件插入到#include所在的位置,把源程序中使用到的用#define定义的宏用实际的字符串代替

    下面列出了生成的部分内容,可以看到stdio.h的内容被插入进来

    ......省略......
    extern int __vsprintf_chk (char * restrict, int, size_t,
          const char * restrict, va_list);
    
    
    extern int __vsnprintf_chk (char * restrict, size_t, int, size_t,
           const char * restrict, va_list);
    # 412 "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk/usr/include/stdio.h" 2 3 4
    # 2 "main.c" 2
    int main(){
        printf("hello world\n");
        return 0;
    }
    

    编译(compilation)

    ➜  gcc -S main.i -o main.s
    

    在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。

    同样列出部分内容,可以看到被转化为汇编语言

    _main:                                  ## @main
        .cfi_startproc
    ## BB#0:
        pushq   %rbp
    Lcfi0:
        .cfi_def_cfa_offset 16
    ......省略......  
    

    汇编(assembly)

    ➜  gcc -c main.s -o main.o
    

    汇编阶段把.s汇编语言文件翻译成二进制机器指令文件.o

    链接(linking)

    ➜  gcc main.s -o main
    

    链接阶段,链接的是函数库
    main.c中并没有定义printf的函数实现,且在预编译中包含进的stdio.h中也只有该函数的声明。系统把这些函数实现都放到名为libc.so的动态库,所以在这个阶段,gcc默认去系统默认路径/usr/local/lib搜索动态库并进行链接,就可以生成可执行文件。

    # 执行可执行文件,输出结果
    ➜  ./main
    hello world
    

    四个阶段一并执行

    上面的为分阶段介绍的过程,一般我们只是执行gcc命令,会包含以上四个阶段

    ➜  gcc main.c -o main2
    ➜  ./main2
    hello world
    # 可以看到与上面的效果一致
    

    查看引用库

    上面链接阶段,说到默认会去搜索本地的动态库,通过otool可以查看引用的库,linux为ldd命令

    # 可以看到引用库id为libSystem.B.dylib,地址为/usr/lib
    ➜  otool -L ./main
    ./main:
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)
    

    去到/usr/lib目录下,可以看到libSystem.B.dylib文件,再执行otool可以看到详细引用库id

    ➜  otool -L ./libSystem.B.dylib
    ./libSystem.B.dylib:
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0)
    ......省略......
    

    .a .so .o 文件的区别

    通过上面的介绍,应该大概知道,.a.so.o文件的区别了,如果还不太理解,可以参考浅析Linux中的.a、.so、和.o文件这篇文章的介绍,通过window的文件去类比介绍

    怎样编译出静态库和动态库

    前面介绍不同后缀文件的区别作为铺垫,接下来,就来到这篇文章的主题

    创建静态库

    ar cr 生成的静态库名称.a   引用的可执行文件.o   [引用的可执行文件.o]
    

    c:创建一个库。不管库是否存在,都将创建。
    r:在库中插入模块(替换)

    创建动态库

    gcc -shared -fPCI 可执行文件.o [可执行文件.o] -o 生成的动态库名称.so 
    

    实例介绍

    接下来,举例详细介绍怎样创建静态库和动态库,并且对比两者的不同点

    创建使用到的程序

    hello.h文件

    #ifndef HELLO_H
    #define HELLO_H
     
    void hello(const char *name);
     
    #endif //HELLO_H
    

    hello.c文件

    #include <stdio.h>
     
    void hello(const char *name)
    {
        printf("Hello %s!\n", name);
    }
    

    main.c文件

    #include "hello.h"
     
    int main()
    {
        hello("world");
        return 0;
    }
    

    把hello.c编译成.o文件

    无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件

    ➜  gcc -c hello.c -o hello.o
    # 查看当前路径下的文件,多了一个hello.o
    ➜  ls
    hello.c hello.h hello.o main.c
    

    创建、使用静态库

    创建静态库

    通过.o可执行文件,创建静态库libhello.o

    ➜  ar cr libhello.a hello.o
    ➜  ls
    hello.c    hello.h    hello.o    libhello.a main.c
    

    使用静态库

    使用静态库,编译main.c源文件,生成hello可执行文件

    ➜  gcc -o hello main.c -L. -lhello
    ➜  ls
    hello      hello.c    hello.h    libhello.a  main.c
    ➜  ./hello
    Hello world!
    
    # 查找库文件
    -LXX  指定库文件查找路径,同一个路径下,为.
    -lXX  指定需要链接的库名,不需要加前缀lib和文件后缀.so
    

    删除libhello.a文件,验证是否hello.c文件中的实现被直接链接到hello

    ➜  rm libhello.a
    # 没有libhello.a,发现还是可以正常输出,印证了上面静态库会在编译阶段完全被整合进代码段中
    ➜  ./hello
    Hello world!
    

    创建、使用动态库

    创建动态库

    ➜  gcc -shared -fPIC -o libhello.so hello.o
    ➜  ls
    hello.c     hello.h     hello.o     libhello.so main.c
    

    使用动态库

    ➜  gcc -o hello main.c -L. -lhello
    ➜  ls
    hello       hello.c     hello.h     hello.o     libhello.so main.c
    ➜  ./hello
    Hello world!
    

    把生成的libhello.so移动到其他路径,再执行

    # 提示加载不到libhello.so库,找不到hello的实现
    # 印证了上面,动态库是在程序运行时动态申请并调用其中的内容
    ➜  ./hello
    dyld: Library not loaded: libhello.so
      Referenced from: /Users/guidongyuan/code/NDKStudy/./hello
      Reason: image not found
    [1]    18773 abort      ./hello
    

    mac os上,如果有通过-L -l去指定路径,那么会优先在指定的路径下找,如果找不到,会去默认路径/usr/local/lib找,可以尝试把libhello.so文件移动到默认路径中,再次执行

    ➜  mv libhello.so /usr/local/lib
    # 成功输出,可验证,会去默认路径寻找
    ➜  ./hello
    Hello world!
    # 同样,通过otool -L查看可执行文件引用库的id,可以发现引用了libhello.so
    ➜  otool -L hello
    hello:
        libhello.so (compatibility version 0.0.0, current version 0.0.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)
    

    参考资料

    相关文章

      网友评论

          本文标题:NDK(三):静态库和动态库

          本文链接:https://www.haomeiwen.com/subject/uhngfqtx.html