2

C++ プログラムのコンパイルとリンクがよくわかりません。C++ プログラムをコンパイルして生成されたオブジェクト ファイルを (わかりやすい形式で) 見る方法はありますか。これは、オブジェクト ファイルの形式、C++ クラスのコンパイル方法、コンパイラがオブジェクト ファイルを生成するために必要な情報を理解し、次のようなステートメントを理解するのに役立ちます。

クラスが入力パラメーターと戻り値の型としてのみ使用される場合、クラス ヘッダー ファイル全体を含める必要はありません。前方宣言で十分ですが、派生クラスが基本クラスから派生する場合は、基本クラスの定義を含むファイルを含める必要があります (「Exceptional C++」から取得)。

オブジェクトファイルのフォーマットを理解するために「Linking and Loading」という本を読んでいますが、C++ ソースコード用に特別に調整されたものを好むでしょう。

ありがとう、

ジャグラティ

編集:

nm を使用すると、オブジェクト ファイルに存在するシンボルを確認できることはわかっていますが、オブジェクト ファイルについてもっと知りたいと思っています。

4

5 に答える 5

1

まず、まず。コンパイラの出力を逆アセンブルしても、問題を理解するのに何の役にも立たないでしょう。コンパイラの出力はもはや C++ プログラムではなく、単純なアセンブリであり、メモリ モデルが何であるかを知らない場合、これは非常に読みにくいものです。

baseの基本クラスであると宣言するときに必要なの定義が必要な理由の特定の問題についてはderived、いくつかの異なる理由があります (おそらく私が忘れている理由は他にもあります)。

  1. タイプのオブジェクトderivedが作成されると、コンパイラは完全なインスタンスとすべてのサブクラス用にメモリを予約する必要があります。base
  2. メンバー属性にアクセスする場合、コンパイラは暗黙的thisなポインターからのオフセットを認識している必要があり、そのオフセットにはbaseサブオブジェクトが使用するサイズを認識している必要があります。
  3. 識別子が のコンテキストで解析され、derivedその識別子がクラスで見つからないderived場合、コンパイラはbase、囲んでいる名前空間で識別子を探す前に、それが定義されているかどうかを知る必要があります。がクラスで宣言されている場合、コンパイラはfoo();内部で有効な呼び出しであるかどうかを知ることができません。derived::function()foo()base
  4. で定義されているすべての仮想関数の数とシグネチャは、コンパイラがクラスbaseを定義するときにわかっている必要があります。動的ディスパッチ メカニズム (通常はderivedvtable) を構築し、メンバ関数がderived動的ディスパッチにバインドされているかどうかを知るためにも、その情報が必要です。キーワード。base::f()derived::f()derivedvirtual
  5. 多重継承では、いくつかの要件が追加されます。たとえばbaseX、メソッドの最終的なオーバーライドが呼び出される前に、それぞれからの相対オフセットを書き換える必要があります (base2オブジェクトを指す型のポインターmultiplyderivedは、インスタンスの先頭ではなく、これは、継承リストでbase2以前に宣言された他のベースによってオフセットされる可能性があります。base2

コメントの最後の質問に:

したがって、オブジェクトのインスタンス化 (グローバルのものを除く) は実行時まで待つことができないため、サイズやオフセットなどはリンク時まで待つことができ、オブジェクト ファイルを生成するときに必ずしもそれを処理する必要はありませんか?

void f() {
   derived d;
   //...
}

derived前のコードは、タイプのオブジェクトをスタックに割り当てます。コンパイラは、アセンブラ命令を追加して、スタック内のオブジェクト用にある程度のメモリを予約します。コンパイラがアセンブリを解析して生成した後、特にオブジェクトの痕跡はなく (POD 型の自明なコンストラクタを仮定すると、つまり何も初期化されていないと仮定すると)、そのコードvoid f() { char array[ sizeof(derived) ]; }はまったく同じアセンブラを生成します。コンパイラーがスペースを予約する命令を生成するとき、その量を知る必要があります。

于 2010-07-09T09:21:12.810 に答える
0

readelf(Linux プラットフォームを使用している場合) でバイナリを調べてみましたか? これは、ELF オブジェクト ファイルに関するかなり包括的な情報を提供します。

正直なところ、これがコンパイルとリンクを理解するのにどれほど役立つかはわかりません。おそらく、C++ コードがアセンブリのプリリンクおよびポストリンクにどのようにマップされるかを理解することが正しい方法だと思います。

于 2010-07-09T07:41:48.563 に答える
0

Obj ファイルは自動的に生成されるため、通常は Obj ファイルの内部形式を詳しく知る必要はありません。知っておく必要があるのは、作成するすべてのクラスに対して、コンパイラが、コンパイル対象の OS に適した、クラスのバイナリ バイト コードである Obj ファイルを生成することだけです。次に、次のステップ - リンク - は、プログラムに必要なすべてのクラスのオブジェクト ファイルを単一の EXE または DLL (または Windows 以外の OS の場合はその他の形式) にまとめます。必要に応じて、EXE + いくつかの DLL にすることもできます。

最も重要なことは、クラスのインターフェイス (宣言) と実装 (定義) を分離することです。

クラスのヘッダー ファイル インターフェイス宣言のみを常に入れます。他には何もありません - ここには実装はありません。ポインターではないカスタム型のメンバー変数も避けてください。それらの場合、前方宣言では不十分であり、ヘッダーに他のヘッダーを含める必要があるためです。ヘッダーにインクルードがあると、デザインが臭くなり、ビルド プロセスが遅くなります。

クラス メソッドまたはその他の関数のすべての実装は、CPP ファイルにある必要があります。これにより、コンパイラによって生成された Obj ファイルが、誰かがあなたのヘッダーをインクルードするときに必要なくなり、CPP ファイルにのみ他の人からのインクルードを含めることができることが保証されます。

しかし、なぜわざわざ?答えは、そのような分離がある場合、各 Obj ファイルがクラスごとに 1 回使用されるため、リンクが高速になるということです。また、クラスを変更すると、次のビルド中に他のオブジェクト ファイルも少し変更されます。

ヘッダーにインクルードがある場合、これは、コンパイラーがクラスの Obj ファイルを生成するときに、ヘッダーに含まれる他のクラスの Obj ファイルを最初に生成する必要があることを意味します。これには、他の Obj ファイルなどが必要になる場合があります。循環依存である可能性もあり、コンパイルできません! または、クラスで何かを変更した場合、分離しないとしばらくすると非常に密接に依存するようになるため、コンパイラは他の多くの Obj ファイルを再生成する必要があります。

于 2010-07-09T07:51:06.660 に答える
0

nmは、オブジェクト ファイル内のシンボルの名前を表示する UNIX ツールです。

objdumpは、より多くの情報を表示する GNU ツールです。

しかし、どちらのツールも、リンカーによって使用される生の情報を表示しますが、人間が読み取るようには設計されていません。これはおそらく、C++ レベルで何が起こっているのかをよりよく理解するのに役立ちません。

于 2010-07-09T08:40:46.353 に答える
0

「 http://www.network-theory.co.uk/docs/gccintro/」 - 「GCCの紹介」を読んでいます。これにより、リンクとコンパイルに関する良い洞察が得られました。初心者レベルですが、気にしません。

于 2010-07-09T09:27:56.803 に答える