静的にリンクされたライブラリ間のシンボルの衝突に対処するにはどうすればよいですか?


82

ライブラリを作成する際の最も重要なルールとベストプラクティスの1つは、ライブラリのすべてのシンボルをライブラリ固有の名前空間に配置することです。C ++は、namespaceキーワードにより、これを簡単にします。Cでは、通常のアプローチは、識別子にライブラリ固有のプレフィックスを付けることです。

ACコンパイラは、識別子の最初の8つの文字を見てもよいので、:C標準のルールは、(安全なコンパイルのために)それらの上にいくつかの制約を置くfoobar2k_eggsfoobar2k_spam有効に同じ識別子として解釈することができる-しかし、すべての近代的なコンパイラは、任意の長い識別子が可能になりますですから、私たちの時代(21世紀)では、これについて気にする必要はありません。

しかし、シンボル名/識別子を変更できないライブラリに直面している場合はどうなりますか?静的バイナリとヘッダーしか取得していないか、取得したくないか、自分で調整して再コンパイルすることが許可されていない可能性があります。


回答:


141

少なくとも静的ライブラリの場合は、非常に便利に回避できます。

ライブラリfoobarのヘッダーを検討してください。このチュートリアルのために、ソースファイルも提供します

examples / 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--;
}

example / 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でそれらを使用したい

example / 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;
}

1つの問題がすぐに明らかになります。Cはオーバーロードを認識していません。したがって、名前は同じで署名が異なる2つの関数が2回あります。したがって、それらを区別する方法が必要です。とにかく、コンパイラがこれについて何を言わなければならないか見てみましょう:

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、ライブラリによってエクスポートされたすべてのシンボルの前に付けるプリプロセッサディレクティブの束で囲みます。後で、いくつかの素敵な居心地の良いラッパーヘッダーを使用してこれを行いますが、何が起こっているのかを示すために、foob​​ar.cソースファイルで逐語的に行っていました。

example / 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

私たちにとって特に興味深いツールが1つあります:objcopy

objcopyは一時ファイルで機能するため、インプレースで動作しているかのように使用できます。--prefix-symbolsと呼ばれる1つのオプション/操作があり、それが何をするかを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を使用して、静的ライブラリのオブジェクトファイルにシンボルプレフィックスを適用します。

共有ライブラリはどうですか?

原則として、共有ライブラリでも同じことができます。ただし、共有ライブラリは、その名前が示すように、複数のプログラム間で共有されるため、この方法で共有ライブラリをいじるのはあまり良い考えではありません。

トランポリンラッパーを書くことは避けられません。さらに悪いことに、オブジェクトファイルレベルで共有ライブラリに対してリンクすることはできませんが、動的ロードを実行する必要があります。しかし、これはそれ自体の記事に値します。

しばらくお待ちください。コーディングをお楽しみください。


4
印象的!でこんなに簡単になるとは思っていませんでしたobjcopy
コス2011

12
質問してから1分以内に自分の質問に答えましたか?
Alex B

18
@Alex B:これはチュートリアルの記事であり、meta.stackoverflow.comで提案されたパスに従って、(興味深い?)質問とその解決策に関するチュートリアルを配置する方法を説明しました。図書館の衝突についての質問が出てきて、「うーん、この種の解決策に対処する方法を知っている」と思い、記事を書いて、Q&Aの形でここに投稿しました。meta.stackexchange.com/questions/97240/...
datenwolf

4
@datenwolfiOSライブラリのこの問題を解決するためのアイデア。私が知ったように、objcopyはiOSライブラリをサポートしていません:/
Ege Akpinar 2013年

6
何とか何とか何とかobjcopy --prefix-symbols ... + 1!
ベンジャクソン

7

C標準のルールはそれらにいくつかの制約を課します(安全なコンパイルのため):ACコンパイラーは識別子の最初の8文字のみを見る可能性があるため、foobar2k_eggsとfoobar2k_spamは有効に同じ識別子として解釈される可能性があります-ただし、すべての最新のコンパイラーは任意の長い識別子なので、私たちの時代(21世紀)では、これについて気にする必要はありません。

これは、最新のコンパイラの単なる拡張ではありません。現在のC標準で、コンパイラが適度に長い外部名をサポートすることも要求されています。正確な長さは忘れてしまいましたが、覚えていれば31文字くらいになりました。

しかし、シンボル名/識別子を変更できないライブラリに直面している場合はどうなりますか?静的バイナリとヘッダーしか取得していないか、取得したくないか、自分で調整して再コンパイルすることが許可されていない可能性があります。

その後、あなたは立ち往生しています。ライブラリの作者に文句を言う。私はかつて、DebianのlibSDLリンクが原因でアプリケーションのユーザーがDebianでアプリケーションをビルドできないというバグに遭遇しました。これはlibsoundfile、(少なくとも当時は)グローバル名前空間をdsp(私はあなたを子供ではありません!)のような変数でひどく汚染しました。私はDebianに不平を言い、彼らはパッケージを修正し、修正をアップストリームに送信しました。問題について二度と聞いたことがなかったので、適用されたと思います。

誰にとっても問題解決するので、これが最善のアプローチだと思います。ローカルでハッキングすると、次の不幸なユーザーが遭遇して再び戦うために、問題がライブラリに残ります。

本当に迅速な修正が必要で、ソースがある場合は-Dfoo=crappylib_foo -Dbar=crappylib_bar、makefileにたくさんのなどを追加して修正できます。そうでない場合は、objcopy見つけたソリューションを使用してください。


もちろんあなたは正しいです、しかし時々あなたは私が上に示したような汚いハックを必要とします。たとえば、ベンダーが廃業したなどのレガシーライブラリで立ち往生している場合です。私はこれを静的ライブラリ用に特別に作成しました。
datenwolf 2011

3

GCCを使用している場合、-allow-multiple-definitionリンカースイッチは便利なデバッグツールです。これにより、リンカーは最初の定義を使用するようになります(そしてそれについて泣き言を言うことはありません)。詳細はこちら

これは、ベンダー提供のライブラリのソースが利用可能であり、何らかの理由でライブラリ関数をトレースする必要がある場合に、開発中に役立ちました。このスイッチを使用すると、ソースファイルのローカルコピーをコンパイルしてリンクし、変更されていない静的ベンダーライブラリにリンクすることができます。発見の航海が完了したら、スイッチをメイクシンボルからヤンクすることを忘れないでください。意図的な名前空間の衝突を伴う出荷リリースコードは、意図しない名前空間の衝突を含む落とし穴になりがちです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.