リンカーとライブラリ

未定義シンボル

すべての入力ファイルを読み取り、シンボル解析がすべて完了すると、リンカーは、シンボル定義に結合されていないシンボルリファレンスの内部シンボルテーブルを検索します。これらのシンボルリファレンスは、未定義シンボルと呼ばれます。これらの未定義シンボルがリンク編集処理に及ぼす影響は、生成される出力ファイルのタイプや、場合によってはシンボルのタイプによって異なります。

実行可能ファイルの作成

リンカーが実行可能ファイルを作成しているときは、リンカーのデフォルトの動作は、シンボルを定義されないままにする必要がある適切なエラーメッセージを表示して、リンク編集を終了させることです。次のように、再配置可能オブジェクト内のシンボル「リファレンス」が、シンボル「定義」と絶対に一致しない場合に、シンボルは定義されないままの状態になります。


$ cat main.c
extern int foo();

main()
{
         return (foo());
}
$ cc -o prog main.c
Undefined           first referenced
 symbol                 in file
foo                     main.o
ld: fatal: Symbol referencing errors. No output written to prog

これと同様の方法で、共有オブジェクトが動的実行可能プログラムを構築するために使用されているときに、共有オブジェクト内のシンボルリファレンスが、シンボル定義と絶対に一致しない場合は、このシンボルリファレンスも未定義シンボルになります。


$ cat foo.c
extern int bar;
foo()
{
        return (bar);
}
 
$ cc -o libfoo.so -G -K pic foo.c
$ cc -o prog main.c -L. -lfoo
Undefined           first referenced
 symbol                 in file
bar                     ./libfoo.so
ld: fatal: Symbol referencing errors. No output written to prog

上記の例のような場合に、未定義シンボルを許可するには、リンカーの -z nodefs オプションを使用することにより、重大なエラー状態を防ぐことができます。


注 -

-z nodefs オプションを使用する場合は、注意が必要です。処理の実行中に使用できないシンボルリファレンスが要求されると、重大な実行時再配置エラーが発生します。このエラーは、初期化の実行中とアプリケーションのテスト中に検出できますが、実行パスが複雑であるために起こったこのエラー状態は、検出に時間がかかり、時間と費用が浪費される場合にがあります。


シンボルは、再配置可能オブジェクト内のシンボルリファレンスが、暗黙の内に定義された共有オブジェクト内のシンボル定義に結合されている場合にも、未定義シンボルのままになる場合があります。たとえば、上記の例で使用したファイル main.c および foo.c に以下のように続く場合です。


$ cat bar.c
int bar = 1;
 
$ cc -o libbar.so -R. -G -K pic bar.c -L. -lfoo
$ ldd libbar.so
        libfoo.so =>     ./libfoo.so
 
$ cc -o prog main.c -L. -lbar
Undefined           first referenced
 symbol                 in file
foo                     main.o  (symbol belongs to implicit ¥
                        dependency ./libfoo.so)
ld: fatal: Symbol referencing errors. No output written to prog

ここで、prog は、libbar.so への明示的なリファレンスを使用して構築されます。また、libbar.so には libfoo.so への依存性があるため、prog から libfoo.so への暗黙的なリファレンスが確立します。

main.c は、libfoo.so によって作成されたインタフェースへの特定のリファレンスを実行するため、prog は、実際に libfoo.so に依存性を持つことになります。ただし、生成される出力ファイル内に記録されるのは、明示的な共有オブジェクトの依存関係だけです。そのため、libbar.so の新しいバージョンが開発され、libfoo.so への依存性がなくなった場合、prog は実行に失敗します。

この理由から、このタイプのバインディングは重大であると考えられ、暗黙的な参照は、prog のリンク編集中にライブラリを直接参照することにより、明示的に実行される必要があります (この例で示した重大なエラーメッセージ内に必要なリファレンスのヒントがあります)。

共有オブジェクトの生成

リンカーが共有オブジェクトを生成する場合、デフォルトにより、未定義シンボルをリンク編集の末尾に残すことができます。これにより、共有オブジェクトはシンボルを、動的実行可能プログラムの構築に使用する場合、再配置可能オブジェクトまたは他の共有オブジェクトのどちらからでもインポートできます。リンカーの -z defs オプションを使用すると、未定義シンボルが残っていた場合に、強制的に重大エラーにすることができます (「追加シンボルの定義」で説明している extern mapfile 指示文も参考にしてください)。

通常、自己組み込み共有オブジェクトは、外部シンボルへのすべてのリファレンスは名前の付いた依存関係によって満たされ、最大の柔軟性が提供がされます。この場合の共有オブジェクトは、共有オブジェクトの必要条件を満たす依存関係を判別し、確立したユーザー以外の、多数のユーザーによって使用されます。

ウィークシンボル

生成中の出力ファイルタイプがどのようなタイプであっても、リンク編集中に結合されないウィークシンボルリファレンスにより、重大なエラー状態が発生します。

静的実行可能プログラムを生成中の場合は、シンボルは絶対シンボルに変換され、ゼロの値が割り当てられます。

動的実行可能プログラムまたは共有オブジェクトの作成中の場合は、シンボルは定義されていないウィークリファレンスとして残されます。プロセスの実行中に、実行時リンカーがこのシンボルを検索し、一致が検出されない場合は、重大な実行時再配置エラーを生成する代わりに、そのリファレンスをゼロのアドレスに結合します。

従来は、これらの定義されていないウィークリファレンスシンボルは、機能の存在をテストするためのメカニズムとして使用されていました。たとえば、次の C コードフラグは、共有オブジェクト libfoo.so.1 内で次のように使用されていました。


#pragma weak    foo

extern  void    foo(char *);

void bar(char * path)
{
         void (* fptr)();

         if ((fptr = foo) != 0)
                (* fptr)(path);
}

アプリケーションがリファレンス libfoo.so.1 で構築されると、シンボル foo の定義が検出されたかどうかに関係なく、リンク編集は、正常に完了します。アプリケーションの実行中に、機能アドレスが非ゼロをテストすると、その機能が呼び出されます。ただし、シンボル定義が検出されない場合には、機能アドレスはゼロをテストするため、その機能は呼び出されません。

ただし、コンパイルシステムは、定義されないセマンティクスを保持しながら、このアドレスの比較テクニックを参照します。その結果、テストステートメントは最適化処理によって削除されます。さらに、実行時シンボルの結合メカニズムでは、このテクニックの使用にこれ以外の制限も加え、これにより、すべての動的オブジェクトが整合性のあるモデルを使用できる状態ではなくなります。

ユーザーは、この方法で定義されていないウィークリファレンスを使用することをやめ、その代わりに RTLD_DEFAULT フラグを指定した dlsym(3DL) を使用してシンボルの存在テストを行うことをお勧めします (「機能のテスト」を参照してください)。