c - 有没有办法让链接器从库中提取目标文件的一部分进行链接?
问题描述
我有一个项目,其中包含数千个 C 文件、许多库和数十个要链接的程序,为了加快编译速度,我将 C 文件组合成包含多个 C 文件的翻译单元。这有时被称为单一编译单元、单一翻译单元或统一构建。
我将这些翻译单元中的多个编译到不同的库中,这些库之前是通过单独编译每个 C 文件来创建的。
例如:
旧库.lib:
file1.o
file2.o
file3.o
file4.o
file5.o
file6.o
新库.lib:
translation_unit_1.o
translation_unit_2.o
翻译单元_1.c:
#include "file1.c"
#include "file2.c"
#include "file3.c"
翻译单元_2.c:
#include "file4.c"
#include "file5.c"
#include "file6.c"
所以这些编译成:translation_unit_1.o 和 translation_unit_2.o。该库是上面显示的新 library.lib。
现在假设我有一个程序,我想链接到 library.lib,它引用 file2.c 中的一个函数。但是它编译的 file1.c 版本不同,它在库中的 file1.c 中重复符号,因此它只需要 library.lib 中的 file2.c 来链接。或者我可能需要从 file1.c 链接代码,但无法链接 file2.c,因为它有一个我不想依赖的依赖项(下面的示例)。
程序:
main.o
file1.o
library.lib
您知道的任何链接器是否有办法让链接器仅将 file2.c 中的代码从 translation_unit_1.o 目标代码中提取出来,并使用它来链接 main.o 以制作程序?
如果可能的话,另一种方法是将 translation_unit_1.o 拆分为 file1.o、file2.o、file3.o,然后将其提供给链接器。
谢谢你的帮助。
编辑 1
这适用于为使用通过 ARM ADS 1.2 工具链编译的 ELF 的裸机 ARM 平台和使用 Visual Studio 工具链的 Windows 平台编译的单一代码库。然而,关于如何在其他平台和工具链上解决问题的想法是受欢迎的。
这是在 MacOS 上使用 clang 的具体示例。
下面的示例代码在这里:https ://github.com/awmorgan/single_translation_unit_lib_link
图书馆:
file1.c 这个文件是需要链接的
file2.c 此文件不用于链接,并且具有未解决的依赖关系,可能位于另一个库或对象中
主.c:
int main( void ) {
extern int file1_a( void );
int x = file1_a();
}
文件1.c:
int file1_a(void) {
return 1;
}
文件2.c:
int file2_a( void ) {
extern int file3_a( void );
return file3_a(); // file3_a() is located somewhere else
}
single_translation_unit.c:
#include "file1.c"
#include "file2.c"
这可以产生program1.out:
++ clang -c file1.c -o file1.o
++ clang -c file2.c -o file2.o
++ libtool -static file1.o file2.o -o library1.lib
++ clang -c main.c -o main1.o
++ clang main1.o library1.lib -o program1.out
这无法产生 program2.out:
++ clang -c single_translation_unit.c -o single_translation_unit.o
++ libtool -static single_translation_unit.o -o library2.lib
++ clang -c main.c -o main2.o
++ clang main2.o library2.lib -o program2.out
Undefined symbols for architecture x86_64:
"_file3_a", referenced from:
_file2_a in library2.lib(single_translation_unit.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
更改链接顺序也不起作用:
++ clang library2.lib main2.o -o program2.out
Undefined symbols for architecture x86_64:
"_file3_a", referenced from:
_file2_a in library2.lib(single_translation_unit.o)
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
解决方案
使用 GCC/binutils ELF 工具链或适当兼容的工具,您可以通过以下方式执行此操作:
single_translation_unit.c
使用选项编译-ffunction-sections
,-fdata-sections
- 使用链接
program2.out
器选项选项进行链接-gc-sections
。
例如(在 Linux 上):
$ gcc -ffunction-sections -fdata-sections -c single_translation_unit.c -o single_translation_unit.o
$ ar rcs library2.a single_translation_unit.o # On Mac OS, use libtool to make the static library if you prefer.
$ gcc -c main.c -o main2.o
$ gcc main2.o library2.a -Wl,-gc-sections -o program2.out
你可以gcc
用clang
整个替换。
链接成功是因为:
- 在编译中,指示编译器在目标文件的不同代码段中发出每个函数定义,不包含任何其他内容,而不是按照默认
-ffunction-sections
将它们全部合并到一个段中。.text
- 在链接中,
-Wl,-gc-sections
指示链接器丢弃未使用的部分,即程序没有引用符号的部分。 - 未引用函数的定义
file2_a
获得了一个不同的代码段,不包含任何其他内容,因此未被使用。链接器能够丢弃这个未使用的部分,以及它file3_a
定义中未解析的引用file2_a
。
所以没有引用file2_a
或file3_a
最终链接,我们可以看到:
$ nm program2.out | egrep '(file2_a|file3_a)'; echo Done
Done
如果我们重新执行请求映射文件的链接:
$ gcc main2.o library2.a -Wl,-gc-sections,-Map=mapfile -o program2.out
然后地图文件将向我们展示:
...
...
Discarded input sections
...
...
.text.file2_a 0x0000000000000000 0xb library2.a(single_translation_unit.o)
...
...
原来的功能部分text.file2.a
确实library2.a(single_translation_unit.o)
被扔掉了。
顺便提一句...
由于在链接中使用静态库的方式,single_translation_unit.o
如果
library2
您library2
知道您的程序
引用single_translation_unit.o
. 您不妨跳过创建library2
,而只是链接single_translation_unit.o
。鉴于需要 中定义的符号single_translation_unit.o
,链接:
$ gcc main2.o library2.a [-Wl,-gc-sections] -o program2.out
与以下链接完全相同:
$ gcc main2.o single_translation_unit.o [-Wl,-gc-sections] -o program2.out
有或没有-Wl,-gc-sections
。
和...
我相信您知道,虽然统一构建对于您的从干净构建来说是最快的,但对于大多数增量构建来说,它可能同样很慢,与自动化构建系统相比,通常是Make based,它精心打造最小化每次源更改所需的重建量。如果您可以从统一构建中受益,那么您可能只能从统一构建以及高效的增量构建中受益。
推荐阅读
- php - 如何模拟未在接口中定义的方法?
- javascript - 点击按钮打开谷歌地图 | 反应
- tensorflow - 创建自定义图层时,Keras 自定义对象的目的是什么?
- java - 为什么在java中将类变量设为私有而将方法设为公有更好
- php - 如何为 apache 2.4.18 安装模块 php7.3
- android - 如何使用 mpandroidchart 在图形滚动时选择条形图/值(类似于三星健康趋势图)
- c++ - qt4:在单个 QGraphicsItem 上调用 update() 会导致所有 QGraphicsItem 上的 paint()
- javascript - 如何将异步(AJAX)函数的结果存储在变量中?
- azure - 将多个 csv 文件中的数据复制到一个 csv 文件中
- javascript - 如何从网页在移动设备上打开 Messenger lite