假设我有以下程序(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);
}
所以,正如我所理解的那样 编译过程 这个程序是:
预处理:将复制粘贴 stdio.h中 和 文件math.h 函数声明和替换 NAME
同 "ashoka"
。
clang -E hello.c
编译: 将转向 C 代码到 部件 码
clang -S hello.c
文件制作: hello.s
组装: 转变 部件 代码到 目的 码
clang -c hello.s
文件制作: hello.o
链接:结合 目的 将文件放入我们将要执行的文件中。
clang hello.o -lm
或者(假设我也想链接hello2.o)
clang hello.o hello2.o
所以,问题来了:
该过程描述的是正确的吗?
在链接阶段,我们链接在一起 .o
(对象代码)文件。我知道 math.h
居住在 /usr/include
目录。哪里 math.o
?链接器如何找到它?
是什么 .a
(静态库)和 .so
Linux中的(动态库)?它们是如何相关的 .o
文件和链接阶段?
假设我想分享一个我与世界建立的图书馆。我有一个 mylib.c
文件,我已经声明并实现了我的功能。我将如何分享这一点,以便人们通过这样做将其包含在他们的项目中 #include <mylib.h>
要么 #include "mylib.h"
?
- 是的,虽然进行汇编是一个额外的步骤(您可以只将C源编译为对象)。在内部,编译器将有更多的阶段:将代码解析为AST,生成中间代码(例如,LLVM bitcode)
clang
),优化等
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
并将其与您的图书馆一起发货。当然,开发人员还必须(对于您的图书馆)进行链接(dinamically)。
静态库的优点是可靠性:没有任何意外,因为您已经将代码与您确定可以使用的确切版本相关联。其他可能有用的情况是,当您使用不常见或前沿的库并且您不希望将它们安装为共享时。这是以增加二进制大小为代价的。
共享库生成较小的二进制文件(因为库不在二进制文件中),RAM占用空间较小(因为操作系统可以加载一次库并在多个进程之间共享),但它们需要更加小心以确保您正在加载正是你想要的(例如,见 DLL地狱 在Windows上)。
正如@iharob所说,他们的优势并不仅仅停留在二进制大小上。例如,如果在共享库中修复了错误,则所有程序都将从中受益(只要它不会破坏兼容性)。此外,共享库提供外部接口和实现之间的抽象。例如,假设OS为应用程序提供了一个库以与其进行交互。通过更新,操作系统界面会发生变化,库实现会跟踪这些更改。如果它被编译为静态库,则必须使用新版本重新编译所有程序。如果它是一个共享库,他们甚至都不会注意到它(只要它 外部 界面保持不变)。另一个示例是Linux库,它将系统/特定于发行版的方面包装到公共接口。