リンカーとライブラリ

未定義シンボル

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

実行可能ファイルの作成

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


$ 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 オプションを使用すると、未定義シンボルが残っていた場合に、強制的に重大エラーにすることができます。共有オブジェクトを作成するときには、このオプションの使用をお勧めします。アプリケーションのシンボルを参照する共有オブジェクトでは、-z defs オプションを使用すれば、extern mapfile 指示文を使ってアプリケーションシンボルを定義できます。これについては、追加シンボルの定義を参照してください。

自己完結型の共有オブジェクトは、外部シンボルへのすべての参照は指定された依存関係によって満たされ、最大の柔軟性が提供されます。この共有オブジェクトは、共有オブジェクトの必要条件を満たす依存関係を判別し確立する手間をユーザーにかけることなく、多数のユーザーによって使用されます。

ウィークシンボル

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

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

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

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


#pragma weak    foo

extern  void    foo(char *);

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

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

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

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


注 –

未定義のウィーク参照をこのように使用することは避けてください。RTLD_DEFAULT フラグを指定した dlsym(3DL) を使用してシンボルの存在テストを行うことをお勧めします。 機能のテストを参照してください。