在C语言中链接文件/头文件

11

假设我有以下程序 (hello.c):

#include <stdio.h>
#include <math.h>

#define NAME "ashoka"


int main(int argc, char *argv[])
{
    printf("Hello, world! My name is %s\n", NAME);
}

所以,据我所理解,编译这个程序的过程是:

  1. 预处理:将复制粘贴 stdio.h math.h 函数声明,并将 NAME 替换为"ashoka"

clang -E hello.c
  • 编译:会将 代码转换成汇编代码

  • clang -S hello.c
    

    文件生成: hello.s

  • 汇编: 将汇编代码转换为目标代码

  • clang -c hello.s
    

    文件产生:hello.o

  • 链接:将目标文件合并成一个我们将要执行的文件。

  • clang hello.o -lm
    

    或者(假设我也想链接hello2.o)

    clang hello.o hello2.o
    

    所以,以下是问题:

    1. 描述的过程是否正确?

    2. 在链接阶段中,我们将.o(目标代码)文件链接在一起。我知道math.h位于/usr/include目录中。那么math.o在哪里?链接器如何找到它?

    3. Linux中的.a(静态库)和.so(动态库)是什么?它们与.o文件和链接阶段有什么关系?

    4. 假设我想与世界分享我制作的库。 我有一个mylib.c文件,在其中声明并实现了我的函数。 我该如何分享此文件,以便人们可以通过#include <mylib.h>#include "mylib.h"将其包含在他们的项目中?


    您的代码将因缺少闭合的 " 而产生编译错误。 - MikeCAT
    @MikeCat 谢谢。已编辑。 - padawanTony
    谢谢大家的回答。在我做了一些研究之后,我还会创建一个关于问题4的新帖子。 - padawanTony
    3个回答

    5
    1. Yes, though going through assembly is an extra step (you can just compile the C source to an object). Internally, the compiler will have many more stages: parsing code into an AST, generating intermediate code (e.g. LLVM bitcode for clang), optimizing, etc.
    2. math.h just defines protypes for the standard math library libm.a (which you link with -lm). The functions themselves live in object files archived inside libm.a (see below).
    3. Static libraries are just archives of object files. The linker will check what symbols are used and will extract and link the object files that export those symbols. Those libraries can be manipulated with ar (for example ar -t lists the object files in a library). Dynamic (or shared) libraries are not included in the output binary. Instead, the symbols your code needs are loaded at runtime.
    4. You would simply create an header file with your externed prototypes:

      #ifndef MYLIB_H
      #define MYLIB_H
      
      extern int mylib_something(char *foo, int baz);
      
      #endif
      

      and ship it with your library. Of course the developer must also link (dinamically) against your library.

    静态库的优势在于可靠性:没有惊喜,因为您已经针对确保其有效的确切版本链接了代码。其他情况下可能有用的是当您使用不常见或最新的库并且不希望将它们安装为共享库时。这是以增加二进制文件大小为代价的。
    共享库生成较小的二进制文件(因为库不在二进制文件中),具有较小的RAM占用量(因为操作系统可以仅加载一次库并在许多进程之间共享它),但是需要更多的注意确保您正在加载确切的内容(例如,请参阅Windows上的DLL Hell)。
    正如@iharob所指出的那样,它们的优点不仅仅在于二进制文件大小。例如,如果在共享库中修复了一个错误,则所有程序都将受益(只要它不破坏兼容性)。此外,共享库提供了外部接口和实现之间的抽象。例如,假设操作系统为应用程序提供了一个库以与其进行接口。随着更新,操作系统接口会发生变化,而库实现会跟踪这些变化。如果它是编译为静态库,所有程序都必须使用新版本重新编译。如果它是共享库,他们甚至不会注意到它(只要外部接口保持不变)。另一个例子是Linux库,它将系统/发行版特定方面包装成一个公共接口。

    我非常喜欢你的回答,但是“你没有加载不兼容的版本”有点误导。可以很安全地假设,如果您的代码针对正确版本的库进行编译并链接,就不会有任何意外情况。此外,共享库除了二进制文件的大小之外,还具有许多优点。例如,一个没有共享库的完整操作系统将是具有挑战性的。 - Iharob Al Asimi
    @iharob 改写了那部分内容,并增加了有关共享库的更多描述。 - Andrea Biondo
    @AndreaBiondo 很棒的回答。但是你让我又有了一个问题。请看一下新创建的问题4。 - padawanTony
    1
    @padawanTony,我在这方面添加了一些信息,但你的问题变得非常广泛(分发共享库确实需要一个单独的答案)。我建议你查找一些关于共享库的教程,它们将指导你进行编译(例如 -fpic-shared 等标志)并使其正常工作(LD_LIBRARY_PATH-rpathldconfig 等)。 - Andrea Biondo

    4

    您上面描述的过程是正确的。然而,在绝大多数情况下,C代码会被预处理并在单个步骤中组装,具体如下:

    clang -c hello.c
    

    通常只有在调试时才会执行单独的预处理操作。将代码转换为汇编语言几乎不会被执行,除非您打算进行一些手动的汇编级别优化,但这很少是必要的。

    关于链接,-l选项告诉链接器查找形式为“lib{name}.so”的共享库。在您的示例中,-lm告诉链接器与libm.so链接。默认情况下,它会在/usr/lib中查找,但您可以使用-L选项来提供一个目录列表,用于搜索库。

    您可以使用-B标志在静态库和动态库之间切换链接:

    clang hello.o -lm -Bstatic -lstaticlib -B dynamic -ldynamiclib
    

    这将链接libm.so、libstaticlib.a和libdynamiclib.so。

    静态库直接链接到可执行文件,就像.o文件一样。相比之下,动态库与可执行文件分开,动态库在运行时被加载。


    很好的回答。但是你让我有了一个新问题。请看一下新创建的问题4。 - padawanTony

    2
    1. 是的,这通常是过程。
    2. 没有 math.o 文件,-lm 开关链接到 libm.so(一个共享对象,因此为 .so),其中所有在 math.h 中声明的数学函数所需的符号都被定义。
    3. 让我们分两个部分回答这个问题

      静态库

         只是以存档格式保存的对象文件集合。

      共享库

         是(在 Linux 上)具有与可执行文件中定义的符号相同的符号的 ELF 文件,您可以链接程序以在运行时使用这些符号,并且有一个加载器将这些符号加载到要使用的程序中。

         这在其他平台上基本相同,例如在 Windows 上的 .dll,它们基本上是编译后缺少 main() 函数的程序,因此不能直接执行。它们包含可执行代码,以便在运行时加载。实际上,您可以使用 dlopen(3) 在 Linux 上自己完成。


    注意:在您发布的代码中,有些事情不会发生,因为您没有使用math.h中的任何内容,所以链接到libm.so是完全不必要的。编译器也会尝试优化生成的代码,在您的情况下,程序等同于中最简单的Hello World。但问题的其他部分是有效的,回答它确实有意义。

    很好的回答。但是你让我有了一个问题。请看一下新创建的问题4。 - padawanTony
    @padawanTony 很高兴能帮忙,让你有更多问题其实是好事。但请提出另一个问题,不要在答案发布后编辑您的问题。 - Iharob Al Asimi

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接