c - 如何处理静态链接库之间的符号冲突?

标签 c static libraries collision symbols

编写库时最重要的规则和最佳实践之一是将所有符号放在 库到特定于库的命名空间。由于 namespace 关键字,C++ 使这变得容易。在 C 通常的方法是在标识符前加上一些特定于库的前缀。

C 标准的规则对这些进行了一些限制(为了安全编译):C 编译器可能只查看第一个 标识符的 8 个字符,因此 foobar2k_eggsfoobar2k_spam 可能被解释为相同 标识符有效——然而每个现代编译器都允许任意长的标识符,所以在我们这个时代 (21 世纪)我们不必为此烦恼。

但是,如果您面对一些无法更改符号名称/标识符的库怎么办?也许你有 只有静态二进制文件和 header 或者不想,或者不允许自己调整和重新编译。

最佳答案

至少对于静态 库,您可以很方便地解决它。

考虑那些库的标题 foobar。为了本教程,我还将为您提供源文件

例子/ex01/foo.h

int spam(void);
double eggs(void);

examples/ex01/foo.c(这可能是不透明的/不可用)

int the_spams;
double the_eggs;

int spam()
{
    return the_spams++;
}

double eggs()
{
    return the_eggs--;
}

例子/ex01/bar.h

int spam(int new_spams);
double eggs(double new_eggs);

examples/ex01/bar.c(这可能不透明/不可用)

int the_spams;
double the_eggs;

int spam(int new_spams)
{
    int old_spams = the_spams;
    the_spams = new_spams;
    return old_spams;
}

double eggs(double new_eggs)
{
    double old_eggs = the_eggs;
    the_eggs = new_eggs;
    return old_eggs;
}

我们想在程序 foobar 中使用它们

例子/ex01/foobar.c

#include <stdio.h>

#include "foo.h"
#include "bar.h"

int main()
{
    const int    new_bar_spam = 3;
    const double new_bar_eggs = 5.0f;

    printf("foo: spam = %d, eggs = %f\n", spam(), eggs() );
    printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", 
            spam(new_bar_spam), new_bar_spam, 
            eggs(new_bar_eggs), new_bar_eggs );

    return 0;
}

一个问题立即变得明显:C 不知道重载。所以我们有两倍的两个函数 相同的名字但不同的签名。所以我们需要一些方法来区分它们。无论如何,让我们看看 编译器必须对此说:

example/ex01/ $ make
cc    -c -o foobar.o foobar.c
In file included from foobar.c:4:
bar.h:1: error: conflicting types for ‘spam’
foo.h:1: note: previous declaration of ‘spam’ was here
bar.h:2: error: conflicting types for ‘eggs’
foo.h:2: note: previous declaration of ‘eggs’ was here
foobar.c: In function ‘main’:
foobar.c:11: error: too few arguments to function ‘spam’
foobar.c:11: error: too few arguments to function ‘eggs’
make: *** [foobar.o] Error 1

好吧,这并不奇怪,它只是告诉我们,我们已经知道,或者至少是怀疑的事情。

那么我们能否在不修改原始库的情况下以某种方式解决标识符冲突 源代码或标题?事实上我们可以。

首先让我们解决编译时间问题。为此,我们用一个 一堆预处理器 #define 指令,它们作为库导出的所有符号的前缀。 稍后我们用一些漂亮舒适的包装头来做到这一点,但只是为了演示 正在发生的事情是在 foobar.c 源文件中逐字执行的:

例子/ex02/foobar.c

#include <stdio.h>

#define spam foo_spam
#define eggs foo_eggs
#  include "foo.h"
#undef spam
#undef eggs

#define spam bar_spam
#define eggs bar_eggs
#  include "bar.h"
#undef spam
#undef eggs

int main()
{
    const int    new_bar_spam = 3;
    const double new_bar_eggs = 5.0f;

    printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() );
    printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n", 
           bar_spam(new_bar_spam), new_bar_spam, 
           bar_eggs(new_bar_eggs), new_bar_eggs );

    return 0;
}

现在如果我们编译这个...

example/ex02/ $ make
cc    -c -o foobar.o foobar.c
cc   foobar.o foo.o bar.o   -o foobar
bar.o: In function `spam':
bar.c:(.text+0x0): multiple definition of `spam'
foo.o:foo.c:(.text+0x0): first defined here
bar.o: In function `eggs':
bar.c:(.text+0x1e): multiple definition of `eggs'
foo.o:foo.c:(.text+0x19): first defined here
foobar.o: In function `main':
foobar.c:(.text+0x1e): undefined reference to `foo_eggs'
foobar.c:(.text+0x28): undefined reference to `foo_spam'
foobar.c:(.text+0x4d): undefined reference to `bar_eggs'
foobar.c:(.text+0x5c): undefined reference to `bar_spam'
collect2: ld returned 1 exit status
make: *** [foobar] Error 1

... 乍一看,事情变得更糟了。但仔细看:其实是编译阶段 一切顺利。现在只是链接器提示有符号冲突 它告诉我们发生这种情况的位置(源文件和行)。正如我们所见 这些符号没有前缀。

让我们用 nm 实用程序看一下符号表:

example/ex02/ $ nm foo.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

example/ex02/ $ nm bar.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams

所以现在我们面临着在一些不透明的二进制文件中为这些符号添加前缀的练习。是的我知道 在这个例子的过程中,我们有源并且可以在那里改变它。但现在,假设 你只有那些 .o 文件,或者一个 .a(实际上只是一堆 .o)。

objcopy 助您一臂之力

我们有一个特别感兴趣的工具:objcopy

objcopy 作用于临时文件,因此我们可以像就地操作一样使用它。有一个 名为 --prefix-symbols 的选项/操作,您有 3 个猜测它的作用。

所以让我们把这个家伙丢给我们顽固的图书馆:

example/ex03/ $ objcopy --prefix-symbols=foo_ foo.o
example/ex03/ $ objcopy --prefix-symbols=bar_ bar.o

nm 向我们展示了这似乎有效:

example/ex03/ $ nm foo.o
0000000000000019 T foo_eggs
0000000000000000 T foo_spam
0000000000000008 C foo_the_eggs
0000000000000004 C foo_the_spams

example/ex03/ $ nm bar.o
000000000000001e T bar_eggs
0000000000000000 T bar_spam
0000000000000008 C bar_the_eggs
0000000000000004 C bar_the_spams

让我们尝试链接整个事情:

example/ex03/ $ make
cc   foobar.o foo.o bar.o   -o foobar

确实,它奏效了:

example/ex03/ $ ./foobar 
foo: spam = 0, eggs = 0.000000
bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000

现在我把它留给读者作为练习来实现一个自动提取 使用 nm 的库的符号,写入结构的包装头文件

/* wrapper header wrapper_foo.h for foo.h */
#define spam foo_spam
#define eggs foo_eggs
/* ... */
#include <foo.h>
#undef spam
#undef eggs
/* ... */

并使用 objcopy 将符号前缀应用于静态库的目标文件。

共享库呢?

原则上,共享库也可以这样做。然而共享库,顾名思义, 在多个程序之间共享,因此以这种方式搞乱共享库并不是一个好主意。

您不会绕过编写 trampoline 包装器。更糟糕的是你不能链接到共享库 在目标文件级别上,但被迫进行动态加载。但这值得单独写一篇文章。

敬请期待,祝您编码愉快。

关于c - 如何处理静态链接库之间的符号冲突?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6940384/

相关文章:

c - 无法删除双链表中的节点

c - 奇怪的控制流程顺序

swift - 方法中使用的协议(protocol)静态变量

php - 在 PHP 中使用 C 库

g++ - 对于实际变换计算,FFTW 是否明显优于 GSL?

java - git - 推送 java .classpath 文件?

从 char 到 int 的转换并打印十进制值

c++ - 如何从 GNU/Linux 中的可执行文件导出特定符号

java - 为什么在线程之间共享静态变量会降低性能?

c# - 在我的字符串列表上使用 Contains() 和 Add() 方法,但它不运行