1. ホーム
  2. c++

[解決済み] オブジェクトファイルとライブラリファイル、その理由は?

2023-06-12 16:55:17

質問

コンパイルの基本は理解しています。 ソースファイルはオブジェクトファイルにコンパイルされ、リンカーはそれを実行ファイルにリンクします。 これらのオブジェクトファイルは、定義を含むソースファイルから構成されています。

そこで質問です。


  • なぜ、ライブラリのために別の実装をするのですか? .a .lib, .dll...
  • 私はおそらく勘違いしているのですが、.o ファイル自体がライブラリと同じようなものであるように思えます。 はライブラリと同じようなものだと思うのですが?
  • ある特定の宣言(.h)の .o 実装を誰かが与えてくれて、それを置き換えて 宣言 (.h) の .o の実装を提供し、それを置き換えてリンクさせれば 同じ機能を実行する実行ファイルになります。 異なるオペレーションを使用する実行可能ファイルになりますか?

どのように解決するのですか?

歴史的に、オブジェクトファイルは実行ファイルに完全にリンクされるか、まったくリンクされないかのどちらかです (現在では 関数レベル リンク または プログラム全体の最適化 が一般的になりつつあります)、そのため、オブジェクトファイルの1つの関数が使用されると、実行ファイルはそれらのすべてを受け取ります。

実行ファイルを小さく保ち、デッドコードをなくすために、標準ライブラリは多くの小さなオブジェクトファイル (通常、数百のオーダー) に分割されています。何百もの小さなファイルを持つことは、効率上の理由から非常に望ましくありません。多くのファイルを開くのは非効率的ですし、どのファイルにも多少のスラック(ファイルの末尾にある未使用のディスクスペース)があります。このため、オブジェクトファイルはライブラリにまとめられ、圧縮されていないZIPファイルのようなものになります。リンク時には、ライブラリ全体が読み込まれ、リンカがライブラリを読み始めたときにすでに未解決とわかっていたシンボルを解決するそのライブラリからのすべてのオブジェクトファイル、またはそれらによって必要とされるオブジェクトファイルが出力に含まれます。これは、依存関係を再帰的に解決するために、ライブラリ全体が一度にメモリ上に存在する必要があることを意味していると思われる。メモリ量がかなり限られていたため、リンカーは一度に 1 つのライブラリしか読み込まず、リンカーのコマンド ラインで後から言及されたライブラリは、コマンド ラインで先に言及されたライブラリの関数を使用することができません。

パフォーマンスを向上させるために(特にフロッピーディスクのような遅いメディアからライブラリ全体をロードするのは時間がかかります)、ライブラリにはしばしば インデックス を含んでおり、リンカにどのオブジェクトファイルがどのシンボルを提供するかを知らせます。インデックスを作成するツールは ranlib やライブラリ管理ツール (Borland の tlib にはインデックスを生成するスイッチがあります)。インデックスがあるとすぐに、すべてのオブジェクト ファイルがディスク キャッシュにあり、ディスク キャッシュからのファイルの読み込みが無料であっても、ライブラリは単一のオブジェクト ファイルよりも確実に効率的にリンクされます。

あなたが完全に正しいのは、私が .o または .a ファイルを作成し、ヘッダーファイルはそのままに、関数の処理内容(または処理方法)を変更します。これを利用するのが LPGL-license を使用するプログラムの作者に要求されるものです。 LGPL-licensed を使用するプログラムの作者に対して、 そのライブラリをパッチされた、改良された、あるいは代替の実装で置き換える可能性を ユーザに提供することを要求しています。ユーザーに必要な自由を与えるには、アプリケーションのオブジェクトファイル (場合によってはライブラリファイルとしてグループ化されたもの) を出荷すれば十分です。 GPL ).

2つのライブラリ(またはオブジェクトファイル)のセットが、同じヘッダファイルで正常に使用できる場合、それらは次のように言われます。 ABI 互換 といいます。ここで、ABI とは アプリケーション バイナリ インターフェイス . これは、単に2組のライブラリ(またはオブジェクトファイル)にそれぞれのヘッダを付属させ、この特定のライブラリのヘッダを使えばそれぞれのライブラリを使えることを保証するよりも狭いものです。これは、次のように呼ばれるでしょう。 API の互換性 ここで、API とは アプリケーション・プログラム・インターフェース . この違いの例として、次の3つのヘッダーファイルを見てください。

ファイル1:

typedef struct {
    int a;
    int __undocumented_member;
    int b;
} magic_data;
magic_data* calculate(int);

ファイル2

struct __tag_magic_data {
    int a;
    int __padding;
    int b;
};
typedef __tag_magic_data magic_data;
magic_data* calculate(const int);

ファイル3.

typedef struct {
    int a;
    int b;
    int c;
} magic_data;
magic_data* do_calculate(int, void*);
#define calculate(x) do_calculate(x, 0)

最初の2つのファイルは同一ではありませんが、(私が期待する限り)"1定義ルール"に違反しない交換可能な定義を提供しているので、ファイル1をヘッダーファイルとして提供するライブラリは、ファイル2をヘッダーファイルとして同様に使用することができます。一方、ファイル 3 はプログラマに非常によく似たインターフェイスを提供しますが(ライブラリの作者がライブラリのユーザに約束することはすべて同じかもしれません)、ファイル 3 でコンパイルしたコードは、ファイル 1 またはファイル 2 で使用するように設計されたライブラリとはリンクできず、ファイル 3 用に設計したライブラリはエクスポートしないでしょうから calculate をエクスポートせず do_calculate . また、構造体のメンバーレイアウトが異なるため、File 3 ではなく File 1 や File 2 を使用すると、b に正しくアクセスできません。File 1 と File 2 を提供するライブラリは ABI 互換ですが,3 つのライブラリとも API 互換です(c とより高機能な関数である do_calculate はその API にカウントされないと仮定しています)。

動的ライブラリ(.dll、.so)については完全に異なります。複数の(アプリケーション)プログラムを同時にロードできるシステムで登場し始めました(DOSではそうではありませんが、Windowsではそうなっています)。ライブラリ関数の同じ実装を何度もメモリ上に置くのは無駄なので、一度だけロードして複数のアプリケーションで利用するのである。ダイナミックライブラリの場合、参照する関数のコードは実行ファイルに含まれず、ダイナミックライブラリ内の関数への参照だけが含まれる(Windows NE/PEでは、どのDLLがどの関数を提供しなければならないかが指定されている。Unixの.soファイルでは、関数名とライブラリのセットのみが指定されています)。オペレーティングシステムには ローダー と呼ばれる ダイナミックリンカー は、これらの参照を解決し、プログラムが開始された時点でまだメモリ内にない場合は動的ライブラリをロードします。