今日、1つのカスタムライブラリを操作しているときに、奇妙な動作が見つかりました。静的ライブラリコードには、デバッグmain()
関数が含まれていました。 _#define
_フラグの中にはありませんでした。そのため、ライブラリにも存在します。そして、実際のmain()
を含む別のプログラムへのリンクに使用されます。
これらの両方がリンクされている場合、リンカはmain()
に対して複数宣言エラーをスローしませんでした。私はこれがどのように起こるのか疑問に思っていました。
簡単にするために、同じ動作をシミュレートするサンプルプログラムを作成しました。
_$ cat prog.c
#include <stdio.h>
int main()
{
printf("Main in prog.c\n");
}
$ cat static.c
#include <stdio.h>
int main()
{
printf("Main in static.c\n");
}
$ gcc -c static.c
$ ar rcs libstatic.a static.o
$ gcc prog.c -L. -lstatic -o 2main
$ gcc -L. -lstatic -o 1main
$ ./2main
Main in prog.c
$ ./1main
Main in static.c
_
「2main」バイナリはどのmain
を実行するかをどのようにして見つけるのですか?
しかし、両方を一緒にコンパイルすると、多重宣言エラーが発生します。
_$ gcc prog.c static.o
static.o: In function `main':
static.c:(.text+0x0): multiple definition of `main'
/tmp/ccrFqgkh.o:prog.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status
_
誰でもこの動作を説明できますか?
Ld(1)の引用:
リンカは、コマンドラインで指定された場所で、アーカイブを1回だけ検索します。コマンドラインでアーカイブの前に表示されたオブジェクトで未定義のシンボルがアーカイブで定義されている場合、リンカーはアーカイブから適切なファイルを含めます。
2mainをリンクすると、ldがprog.oからピックアップするため、ldが-lstaticに達する前にメインシンボルが解決されます。
1mainをリンクするとき、-lstaticに達するまでにmainが未定義なので、mainのアーカイブを検索します。
このロジックは、通常のオブジェクトではなく、アーカイブ(静的ライブラリ)にのみ適用されます。 prog.oとstatic.oをリンクすると、両方のオブジェクトのすべてのシンボルが無条件に含まれるため、重複定義エラーが発生します。
静的ライブラリ(.a)をリンクすると、リンカーは、これまでに追跡された未定義のシンボルがある場合にのみアーカイブを検索します。それ以外の場合、アーカイブはまったく表示されません。したがって、あなたの2main
ケースでは、翻訳単位を作成するための未定義のシンボルがないため、アーカイブを参照することはありません。
static.c
に単純な関数を含める場合:
#include <stdio.h>
void fun()
{
printf("This is fun\n");
}
int main()
{
printf("Main in static.c\n");
}
prog.c
から呼び出すと、リンカーはアーカイブを見てシンボルfun
を見つけることを強制され、リンカーが重複シンボルmain
を見つけるのと同じ複数のメイン定義エラーが発生します。
オブジェクトファイルを直接コンパイルすると(gcc a.o b.o
のように)、ここでリンカーには役割がなく、すべてのシンボルが含まれて単一のバイナリを作成し、明らかにシンボルが重複します。
一番下の行は、シンボルが欠落している場合にのみリンカがアーカイブを見るということです。それ以外の場合は、ライブラリとリンクしないのと同じです。
リンカーは、オブジェクトファイルを読み込んだ後、ライブラリで未定義のシンボルを検索します。存在しない場合、ライブラリを読み込む必要はありません。 mainが定義されているため、たとえすべてのライブラリでmainが見つかったとしても、2番目のものをロードする理由はありません。
ただし、リンカーの動作は劇的に異なります。たとえば、ライブラリにmain()とfoo()の両方を含むオブジェクトファイルが含まれていて、fooが未定義の場合、乗算定義シンボルmain()でエラーが発生する可能性が非常に高くなります。
現代の(トートロジー)リンカーは、到達不能なオブジェクトからグローバルシンボルを省略します。 AIX。 SolarisやLinuxシステムで見られるような古いスタイルのリンカーは、1970年代のUNIXリンカーのように動作し、オブジェクトモジュールからすべてのシンボルを読み込み可能かどうかを読み込みます。これは、過度のリンク時間と同様に恐ろしい膨張の原因になる可能性があります。
また、* nixリンカーの特徴は、リストされるたびにライブラリを1回だけ効率的に検索することです。これにより、プログラムの作成に加えて、コマンドラインでリンカーまたはメイクファイル内のライブラリを注文することがプログラマに要求されます。ライブラリの順序付けられたリストを必要としないことは現代的ではありません。古いオペレーティングシステムには、パスがシンボルの解決に失敗するまで、すべてのライブラリを繰り返し検索するリンカーがよくありました。