问题 在C中链接文件/标题


假设我有以下程序(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
    
  2. 编译: 将转向  代码到 部件 码

    clang -S hello.c
    

    文件制作: hello.s

  3. 组装: 转变 部件 代码到 目的 码

    clang -c hello.s
    

    文件制作: hello.o

  4. 链接:结合 目的 将文件放入我们将要执行的文件中。

    clang hello.o -lm
    

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

    clang hello.o hello2.o
    

所以,问题来了:

  1. 该过程描述的是正确的吗?

  2. 在链接阶段,我们链接在一起 .o (对象代码)文件。我知道 math.h 居住在 /usr/include 目录。哪里 math.o?链接器如何找到它?

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

  4. 假设我想分享一个我与世界建立的图书馆。我有一个 mylib.c 文件,我已经声明并实现了我的功能。我将如何分享这一点,以便人们通过这样做将其包含在他们的项目中 #include <mylib.h> 要么 #include "mylib.h"


6798
2017-08-27 12:48


起源

由于没有关闭,您的代码将发出编译错误 " - MikeCAT
@MikeCat thx。编辑。 - padawanTony
谢谢大家的答案。在我做了一些研究之后,我还将创建一个关于问题4的新帖子。 - padawanTony


答案:


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

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

    并将其与您的图书馆一起发货。当然,开发人员还必须(对于您的图书馆)进行链接(dinamically)。

静态库的优点是可靠性:没有任何意外,因为您已经将代码与您确定可以使用的确切版本相关联。其他可能有用的情况是,当您使用不常见或前沿的库并且您不希望将它们安装为共享时。这是以增加二进制大小为代价的。

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

正如@iharob所说,他们的优势并不仅仅停留在二进制大小上。例如,如果在共享库中修复了错误,则所有程序都将从中受益(只要它不会破坏兼容性)。此外,共享库提供外部接口和实现之间的抽象。例如,假设OS为应用程序提供了一个库以与其进行交互。通过更新,操作系统界面会发生变化,库实现会跟踪这些更改。如果它被编译为静态库,则必须使用新版本重新编译所有程序。如果它是一个共享库,他们甚至都不会注意到它(只要它 外部 界面保持不变)。另一个示例是Linux库,它将系统/特定于发行版的方面包装到公共接口。


5
2017-08-27 13:11



我非常喜欢你的答案,但是 你没有加载不兼容的版本 有点误导。可以非常安全地假设,如果您的代码是根据库的正确版本编译并与之链接的,那么就不会有任何意外。此外,除了二进制文件的大小之外,共享库与静态库相比具有许多优点。例如,没有共享库的完整操作系统将具有挑战性。 - Iharob Al Asimi
@iharob重新编写了该部分并在共享库中添加了更多内容。 - Andrea Biondo
@AndreaBiondo很棒的回答。但是你让我又有一个问题。请看新创建的问题4。 - padawanTony
@padawanTony添加了一些关于它的信息,但是你在这里得到了相当广泛的分布(分发共享库真的需要一个单独的答案)。我建议你查阅一些关于共享库的教程,他们会引导你完成编译(比如像 -fpic, -shared等)并使其工作(LD_LIBRARY_PATH, -rpath, ldconfig等)。 - Andrea Biondo


您在上面描述的过程是正确的。然而,在绝大多数情况下,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
2017-08-27 13:05



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


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

    静态库

    是简单的目标文件集合以归档格式保存。

    共享库

    是 (在linux上)ELF文件的符号定义类似于在可执行文件中定义,您链接程序以便能够在运行时使用这些符号,并且有一个加载器将这些符号加载到要使用的程序中。

    这在其他平台上几乎是一样的,比如 .DLL在Windows上,它们基本上是编译的程序,缺少一个 主要() 功能,所以他们不能直接执行。 Thye包含要在运行时加载的可执行代码。事实上你可以通过使用来实现 dlopen(3) 在linux上。


注意:在你发布的代码中,有些东西不会发生,因为你没有使用任何东西 文件math.h所以链接到 libm.so 是完全没有用的。编译器还尝试优化生成的代码,在您的情况下,程序相当于最简单的 你好,世界 在 。但问题的其余部分是有效的,回答是有意义的。


2
2017-08-27 13:05



很好的答案。但是你让我又有一个问题。请看新创建的问题4。 - padawanTony
@padawanTony很高兴帮助,让你有更多的问题实际上是好的。但请问另一个问题。在已经发布答案后,请不要编辑您的。 - Iharob Al Asimi