在C中链接文件/标题

pad*_*ony 11 c linux linker compilation clang

假设我有以下程序(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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

    clang -E hello.c
    
    Run Code Online (Sandbox Code Playgroud)
  2. 编译:将代码转换为汇编代码

    clang -S hello.c
    
    Run Code Online (Sandbox Code Playgroud)

    生成的文件:hello.s

  3. 组装:将汇编代码转换为目标代码

    clang -c hello.s
    
    Run Code Online (Sandbox Code Playgroud)

    生成的文件:hello.o

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

    clang hello.o -lm
    
    Run Code Online (Sandbox Code Playgroud)

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

    clang hello.o hello2.o
    
    Run Code Online (Sandbox Code Playgroud)

所以,问题来了:

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

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

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

  4. 假设我想与世界共享一个图书馆.我有一个mylib.c文件,我在其中声明并实现了我的功能.我怎么会去分享这个让人们会做将它包含在他们的项目#include <mylib.h>还是#include "mylib.h"

And*_*ndo 5

  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
    
    Run Code Online (Sandbox Code Playgroud)

    并将其与您的图书馆一起发货.当然,开发人员还必须(与你的库)链接(dinamically).

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

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

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