在C语言中链接文件/标题

在C语言中链接文件/标题,c,linux,compilation,linker,clang,C,Linux,Compilation,Linker,Clang,假设我有以下程序(hello.c): 编译:将代码转换为汇编代码 生成的文件:hello.s 汇编:将汇编代码转换为目标代码 生成的文件:hello.o 链接:将目标文件合并到一个我们将执行的文件中 clang hello.o -lm 或者(假设我也想链接hello2.o) 因此,问题来了: 描述的过程是否正确 在链接阶段,我们将.o(目标代码)文件链接在一起。我知道math.h位于/usr/include目录中。哪里是math.o?链接器是如何找到它的 Linux中的.a(静态库)和.

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

  • 编译:将代码转换为汇编代码

    生成的文件:hello.s

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

    生成的文件:hello.o

  • 链接:将目标文件合并到一个我们将执行的文件中

    clang hello.o -lm
    
    或者(假设我也想链接hello2.o)


  • 因此,问题来了:

  • 描述的过程是否正确

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

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

  • 比如说,我想与全世界分享我制作的图书馆。我有一个
    mylib.c
    文件,其中声明并实现了我的函数。我将如何分享这些信息,以便人们通过执行
    \include
    \include“mylib.h”
    将其包含在项目中

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

    静态库

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

    共享库

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

    这在其他平台上几乎是一样的,比如windows上的.dll,它们基本上是编译程序,缺少main()函数,因此无法直接执行。包含要在运行时加载的可执行代码。事实上,您可以在linux上使用



  • 注意:在您发布的代码中,有些事情不会发生,因为您没有使用math.h中的任何内容,因此链接到libm.so完全没有必要。编译器还试图优化生成的代码,在您的情况下,该程序相当于世界上最简单的Hello。但问题的其余部分是有效的,回答起来确实有意义。

    您上面描述的过程是正确的。然而,在绝大多数情况下,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文件一样直接链接到可执行文件。相反,动态库与可执行文件分开保存,并在运行时加载

  • 是的,尽管完成汇编是一个额外的步骤(您只需将C源代码编译成一个对象)。在内部,编译器将有更多的阶段:将代码解析为AST、生成中间代码(例如,
    clang
    )的LLVM位代码、优化等
  • math.h
    只定义标准数学库
    libm.a
    (您可以链接到
    -lm
    )的原型。函数本身存在于libm.a中存档的对象文件中(见下文)
  • 静态库只是对象文件的存档。链接器将检查使用了哪些符号,并将提取和链接导出这些符号的对象文件。可以使用
    ar
    操作这些库(例如
    ar-t
    列出库中的目标文件)。输出二进制文件中不包括动态(或共享)库。相反,代码所需的符号将在运行时加载
  • 您只需使用
    extern
    ed原型创建一个头文件:

    #ifndef MYLIB_H
    #define MYLIB_H
    
    extern int mylib_something(char *foo, int baz);
    
    #endif
    
    并将其与您的库一起发送。当然,开发人员还必须(友好地)链接到您的库

  • 静态库的优点是可靠性:这不会让人感到意外,因为您已经将代码链接到了您确定可以使用的确切版本。其他可能有用的情况是,当您使用不常见或最先进的库,并且不希望将它们作为共享库安装时。这是以增加二进制大小为代价的

    共享库产生更小的二进制文件(因为库不在二进制文件中)和更小的RAM占用空间(因为操作系统可以加载库一次并在多个进程之间共享),但它们需要更加小心,以确保加载的正是您想要的(例如,请参见Windows上的)

    正如@iharob所指出的,它们的优势不仅仅局限于二进制大小。例如,如果在共享库中修复了一个bug,所有程序都将从中受益(只要它不破坏兼容性)。此外,共享库提供了外部接口和实现之间的抽象。例如,假设一个操作系统为应用程序提供了一个与之接口的库。和你
    clang hello.o -lm
    
    clang hello.o hello2.o
    
    clang -c hello.c
    
    clang hello.o -lm -Bstatic -lstaticlib -B dynamic -ldynamiclib
    
    #ifndef MYLIB_H
    #define MYLIB_H
    
    extern int mylib_something(char *foo, int baz);
    
    #endif