C 忽略链接器/加载程序如何使用静态库和动态库之间的差异

C 忽略链接器/加载程序如何使用静态库和动态库之间的差异,c,gcc,compiler-construction,static-linking,dynamic-linking,C,Gcc,Compiler Construction,Static Linking,Dynamic Linking,我了解链接器/加载程序如何使用静态/动态库 但是,为什么不使用单一类型的库文件,并附带编译器标志来指示库应如何链接(静态与动态) 简单地说,我们有静态和动态库,我假定这些文件有特定的内容,分别支持静态和动态链接。有人能解释一下静态库文件和共享库文件内容之间的区别吗 在运行时加载的动态库的格式由操作系统编写器确定。静态库的格式由工具链编写器设置。这些程序员类之间通常有一些重叠,但他们倾向于保持关注点的分离 运行时加载程序需要知道要加载的映像的大小,可能是一些堆栈和数据段的大小,以及DLL中函数的名

我了解链接器/加载程序如何使用静态/动态库

  • 但是,为什么不使用单一类型的库文件,并附带编译器标志来指示库应如何链接(静态与动态)
  • 简单地说,我们有静态和动态库,我假定这些文件有特定的内容,分别支持静态和动态链接。有人能解释一下静态库文件和共享库文件内容之间的区别吗
  • 在运行时加载的动态库的格式由操作系统编写器确定。静态库的格式由工具链编写器设置。这些程序员类之间通常有一些重叠,但他们倾向于保持关注点的分离
  • 运行时加载程序需要知道要加载的映像的大小,可能是一些堆栈和数据段的大小,以及DLL中函数的名称和入口点。链接器需要更多地了解静态库中归档的每个对象(函数/数据)。函数签名、数据类型、大小、初始化、访问范围等

  • 每个操作系统和工具链都有自己的特定需求和修订历史,因此在这里为所有操作系统和工具链描绘精确的文件布局是不切实际的。有关详细信息,请参阅操作系统和工具链文档。

    可共享库往往被编译成独立于位置的代码。这意味着,例如,控制传输使用PC相对寻址。这是因为它们可能被映射到不同客户端程序地址空间中的不同位置

    可共享库应该在只读代码+数据和读/写数据之间有一个清晰的分隔。这样,对于所有客户端进程,只需将只读部分的一个副本加载到内存中

    如果客户端程序A指的是库B和库C,而库B也指的是库C,那么您必须确保所有内容都是使用一致的共享性设置构建的。例如,如果C构建在可共享和不可共享版本中,而B构建在可共享版本的C中,那么A也必须构建在可共享版本的B和C中。如果它尝试使用不可共享版本的C,这可能会导致构建问题,因为与B使用的C的可共享版本冲突


    Ulrich Drepper(GNU libc库的前维护者)对可共享库进行了比您想知道的更多的详细介绍。

    ,因为它们是完全不同的东西。静态库只是编译器生成的目标代码的集合。动态库是链接的

    遗憾的是,静态库和动态库这两个术语是 这两种形式都是形容词库,因为它永远引导程序员 认为它们表示本质上相同事物的变体。 这几乎和羽毛球法庭和最高法院一样具有误导性 本质上是同一种东西。事实上,这更具误导性, 因为没有人会认为羽毛球场和最高法院 法庭本质上是同一种东西

    有人能解释一下静态库文件和共享库文件内容之间的区别吗

    让我们举个例子。击退羽毛球场/最高法院的大雾 我将使用更准确的技术术语。我要说的不是静态库,而是归档,而不是动态库 动态共享对象,简称DSO

    什么是
    ar
    存档

    我将从以下三个文件开始创建一个
    ar
    存档:

    foo.c

    #include <stdio.h>
    
    void foo(void)
    {
        puts("foo");
    }
    
    #include <stdio.h>
    
    void bar(void)
    {
        puts("bar");
    }
    
    extern void foo(void);
    
    int main(void)
    {
        foo();
        return 0;
    }
    
    extern void foo(void);
    extern void bar(void);
    
    int main(void)
    {
        foo();
        bar();
        return 0;
    }
    
    extern void foo(void);
    extern void bar(void);
    
    void foobar(void)
    {
        foo();
        bar();
    }
    
    我将把这两个C源代码编译成位置独立的对象文件:

    $ gcc -c -Wall -fPIC foo.c
    $ gcc -c -Wall -fPIC bar.c
    
    无需使用编译目标为
    ar
    存档的对象文件
    -fPIC
    。我只想让这些文件按这种方式编译

    然后我将创建一个名为
    libsundry.a
    ar
    存档,其中包含对象文件
    foo.o
    bar.o
    , plus
    limerick.txt

    $ ar rcs libsundry.a foo.o bar.o limerick.txt
    
    当然,创建
    ar
    存档时, GNU通用档案管理员。因此,它不是由链接器创建的。无链接 发生。以下是
    ar
    报告归档内容的方式:

    $ ar -t libsundry.a 
    foo.o
    bar.o
    limerick.txt
    
    档案中的打油诗是这样的:

    $ rm limerick.txt
    $ ar x libsundry.a limerick.txt; cat limerick.txt
    There once was a young lady named bright
    Whose speed was much faster than light
    She set out one day
    In a relative way
    And returned on the previous night.
    
    Q.将两个对象文件和一个ASCII打油诗放入同一个
    ar
    归档文件有什么意义

    A.表明我可以。显示
    ar
    归档文件只是一包文件

    让我们看看
    libsundry.a.

    $ file libsundry.a 
    libsundry.a: current ar archive
    
    现在我将编写两个程序,在它们的链接中使用
    libsundry.a

    fooprog.c

    #include <stdio.h>
    
    void foo(void)
    {
        puts("foo");
    }
    
    #include <stdio.h>
    
    void bar(void)
    {
        puts("bar");
    }
    
    extern void foo(void);
    
    int main(void)
    {
        foo();
        return 0;
    }
    
    extern void foo(void);
    extern void bar(void);
    
    int main(void)
    {
        foo();
        bar();
        return 0;
    }
    
    extern void foo(void);
    extern void bar(void);
    
    void foobar(void)
    {
        foo();
        bar();
    }
    
    编译、链接并运行该文件:

    $ gcc -c -Wall fooprog.c
    $ gcc -o fooprog fooprog.o -L. -lsundry
    $ ./fooprog 
    foo
    
    那真是帅哥多莉。显然,该链接器并没有因为存在
    libsundry.a
    中的ASCII打油诗

    原因是链接器甚至没有尝试链接
    limerick.txt
    进入程序。让我们再次进行链接,这次使用诊断选项 这将准确显示链接的输入文件:

    $ gcc -o fooprog fooprog.o -L. -lsundry -Wl,-trace
    /usr/bin/ld: mode elf_x86_64
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
    /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
    fooprog.o
    (./libsundry.a)foo.o
    -lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
    /lib/x86_64-linux-gnu/libc.so.6
    (/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
    /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    -lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
    /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
    
    那里有很多默认库和对象文件,但唯一的对象 我们创建的链接器使用的文件是:

    fooprog.o
    (./libsundry.a)foo.o
    
    链接器对
    /libsundry.a所做的一切都是从
    打开包并将其链接到程序中。将
    fooprog.o
    链接到程序后, 它需要找到
    foo
    的定义。 它看起来在包里。它在
    foo.o
    中找到了定义,因此它从 把包打开,并在程序中链接它。链接
    foobarprog.o
    (./libsundry.a)foo.o
    (./libsundry.a)bar.o
    
    gcc -o foobarprog foobarprog.o -L. -lsundry
    
    $ gcc -o foobarprog foobarprog.o foo.o bar.o
    
    extern void foo(void);
    extern void bar(void);
    
    void foobar(void)
    {
        foo();
        bar();
    }
    
    $ gcc -c -Wall -fPIC foobar.c
    
    $ gcc -shared -o libfoobar.so foobar.o -L. -lsundry -Wl,-trace
    /usr/bin/ld: mode elf_x86_64
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
    /usr/lib/gcc/x86_64-linux-gnu/5/crtbeginS.o
    foobar.o
    (./libsundry.a)foo.o
    (./libsundry.a)bar.o
    -lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
    /lib/x86_64-linux-gnu/libc.so.6
    /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    -lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
    /usr/lib/gcc/x86_64-linux-gnu/5/crtendS.o
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
    
    foobar.o
    (./libsundry.a)foo.o
    (./libsundry.a)bar.o
    
    $ ar -t libfoobar.so 
    ar: libfoobar.so: File format not recognised
    
    $ file libfoobar.so 
    libfoobar.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), \
    dynamically linked, BuildID[sha1]=16747713db620e5ef14753334fea52e71fb3c5c8, \
    not stripped
    
    $ gcc -o foobarprog foobarprog.o -L. -lfoobar -Wl,-trace,--rpath=$(pwd)
    /usr/bin/ld: mode elf_x86_64
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o
    /usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o
    foobarprog.o
    -lfoobar (./libfoobar.so)
    -lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
    /lib/x86_64-linux-gnu/libc.so.6
    (/usr/lib/x86_64-linux-gnu/libc_nonshared.a)elf-init.oS
    /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    -lgcc_s (/usr/lib/gcc/x86_64-linux-gnu/5/libgcc_s.so)
    /usr/lib/gcc/x86_64-linux-gnu/5/crtend.o
    /usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crtn.o
    
    foobarprog.o
    -lfoobar (./libfoobar.so)
    
    $ ldd foobarprog
        linux-vdso.so.1 =>  (0x00007ffca47fb000)
        libfoobar.so => /home/imk/develop/so/scrap/libfoobar.so (0x00007fb050eeb000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb050afd000)
        /lib64/ld-linux-x86-64.so.2 (0x000055d8119f0000)
    
    $ ./foobarprog
    foo
    bar
    
    $ gcc -pie -o foobarprog foobarprog.o -L. -lfoobar -Wl,--rpath=$(pwd)
    
    $ file foobarprog
    foobarprog: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), ....
    
    $ ./foobarprog
    foo
    bar