2

たとえば、main.cpp と mustcomefirst.cpp があるとします。main.cpp にはエントリ ポイント関数があります。int main()

コンパイラは(一般に)次のように機能すると考えるのは正しいでしょうか。最初にエントリポイント関数を含むファイルを検索し、次にすべてのインクルード関数行をファイルのコードに置き換えて、あたかもそうであるか#include mustcomefirst.cppのようにインクルードしますt というファイルに書いてありますが、mustcomefirst.cpp というファイルのコードがそこに書かれていて、そこからエントリポイント関数に行き、そこから順番に連続して実行されますか?

プロジェクトにファイルをインクルードしても、エントリ ポイント機能を持つファイルがそのファイルを (直接的または間接的に) インクルードしなければ意味がありませんか?

4

3 に答える 3

2

いいえ、コンパイラは一般int main()に、ソース ファイルのいずれかに があるかどうか、またはどのソース ファイルにあるかを気にしませmainん ( のような無効な があるかどうかは気にしますvoid main())。すべてのソースファイルmain.

3 つのソース ファイルを含むプロジェクトをコンパイルすると、次のようになります。

g++ file1.cpp file2.cpp file3.cpp

それらのそれぞれは、あたかもあなたが行ったかのように、個別にオブジェクト コードに変換されます。

g++ -c file1.cpp; g++ -c file1.cpp; g++ -c file1.cpp

次に、リンカーによってリンクされます-手動で行うことができます

g++ file1.o file2.o file3.o -o myprogram.

mainここで、プログラムに があることを確認するのはリンカの仕事です。何もない場合はundefined referenceエラーが発生しますが、コンパイルが完了してからずっと後にすべてが発生します。

(「プロジェクトにファイルを含める」について話すと、質問があいまいになります。 について話している場合#include、それは1つの翻訳単位にのみコードを含めます。それとは別に、「プロジェクト」はC ++で明確に定義された概念ではありませんただし、IDE またはビルド システムが使用する抽象化 - これは、「プロジェクト」に配置したすべてのソース ファイルがコンパイルされ、上記の例のようにリンクされることを意味します。)

于 2013-03-23T00:54:42.833 に答える
1

まず、C++ プログラムがどのようにコンパイルされるかを大まかに理解します。main.cppプロジェクトに 、foo.cpp、およびの 3 つのファイルがある場合、次foo.hのようにプロジェクトをコンパイルできます。

g++ main.cpp foo.cpp

これら 2 つのファイルは別々にコンパイルされます。プリプロセッサとコンパイラは、これらのファイル間の関係についてまったく気にしません。前処理とコンパイルmain.cppの結果はオブジェクト ファイルになり、前処理とコンパイルfoo.cppの結果は別のオブジェクト ファイルになります。

関係が考慮されるのは、最後のステップであるリンクのステップだけです。リンカーmainは で定義されていることを確認し、それmain.cppをプログラムの開始点にします。リンカーは、内部で定義されてmain.cppいる関数への呼び出しがあることを確認し、それらをリンクします。foofoo.cpp

そのため、コンパイラに渡すファイルごとに、次の 2 つの主な手順があります。

  1. 前処理 - ファイルは非常に原始的な方法で処理され、その結果トークンが前処理されます。これらのトークンの一部は、または#などの で始まる前処理ディレクティブです。これらのディレクティブが実行されます。ディレクティブを実行すると、名前付きファイルの内容が現在のファイルにコピーされるだけです。#include#define#include

    前処理段階では、いわゆる翻訳単位が生成されます。

  2. コンパイル - ファイルは構文的および意味的に分析され、オブジェクト ファイルに変換されます。ここから、コードが正確に何をしようとしているのか、それが正しいかどうかを気にし始めます。宣言されていない名前を使用しようとすると、コンパイラが警告します。ただし、別の翻訳単位で定義されている可能性があるため、定義されていない名前を使用できることがよくあります。

    このコンパイル段階では、オブジェクト ファイルが生成されます。

この後、生成されたオブジェクト ファイルは、それらの間の参照を解決することによって結合されます。に対応するオブジェクト ファイルが で定義されてmain.cppいる関数を使用する場合、それらは互いにリンクされます。foofoo.cpp


わかりやすくするために、例を見てみましょう。次のソース ファイルがあるとします。

  • main.cpp

    #include "foo.h"
    
    int main() {
      foo();
    }
    
  • foo.h

    void foo();
    
  • foo.cpp

    #include <iostream>
    #include "foo.h"
    
    void foo() {
      std::cout << "Foo!" << std::endl;
    }
    

そのため、コマンド ラインでmain.cppandをコンパイラに渡します。foo.cppヘッダー ファイルをコンパイラに渡さないことに注意してください。それらはファイルによってのみ含まれ.cppます。.cppまた、ファイルには通常、他のファイルは含まれないことに注意してください.cpp。各.cppファイルは個別にコンパイルされ、後でリンクされます。

したがって、前処理段階の後、2 つの翻訳単位があります。

  • main.cpp翻訳単位

    void foo();
    
    int main() {
      foo();
    }
    
  • foo.cpp翻訳単位

    // contents of <iostream> here
    void foo();
    
    void foo() {
      std::cout << "Foo!" << std::endl;
    }
    

の内容がfoo.h各ファイルにコピーされていることに注意してください。これで、2 つの完全に有効な翻訳単位ができました。1 つ目は関数を定義しmain、宣言のみの関数を呼び出すだけfooです。2 番目は最初に宣言しfoo、その直後に定義します。

次に、翻訳単位がコンパイルされてオブジェクト ファイルが生成されます。これらのファイルは、多くの場合、 および と呼ばれmain.oますfoo.o。次に、リンカーは、ファイル内の特定の未解決の参照を調べます。たとえば、fooinの呼び出しを見つけて、main.oまだ定義されていないことを確認します。そのため、他のオブジェクト ファイルを調べて、それを見つけることができるかどうかを確認しfoo.oます。

于 2013-03-23T01:01:51.670 に答える
0

コンパイラは、エントリ ポイント関数を気にしません。ローダーが行います ( http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html )。

非常に大まかに、(.cpp/.c などの各ソース ファイルに対して) コンパイラのタスクは次のとおりです。

  1. ファイルを前処理します (ヘッダー (.h、.hpp など) を含め、マクロを置き換えます)。この段階で、ソース ファイルに含まれるすべてのヘッダー ファイルがソース ファイルにインクルード (一種のコピー アンド ペースト) されます。すべてのソース ファイルは個別にコンパイルされ、他のソース ファイルに含まれている/定義されているものとは関係ありません。
  2. 解析し、プログラムを抽象構文木に変換します
  3. 構文エラーと型エラーをチェックします。
  4. プログラムを中間表現 (IR) に変換します。
  5. コンパイラのバックエンドは IR を受け取り、プログラムを最適化します。
  6. その後、バックエンドは最適化された IT をターゲット固有のマシン コード (オブジェクト ファイル) に変換します。

----- これでコンパイラの仕事は終わりです。他のタスクに加えて、リンカー ( http://en.wikipedia.org/wiki/Linker_(computing)main )は、プログラムに関数があるかどうかのみを検索します。どのオブジェクト ファイルにも main() 関数がない場合、リンカー エラーが発生します。一般に、実行可能ファイルを生成するためにリンクされる一連のオブジェクト ファイルには、各関数の定義が 1 つだけ存在する必要があります。

----- 実行可能ファイルは、実行/実行できるリンカーによって生成されます。

----- 実行ローダーを開始するには、プログラムをメインメモリにロードし、main()関数を呼び出します。

詳細を知るには、実行可能ファイルで objdump を実行するだけで、メインの前に呼び出される多くの関数が見つかります。

Linuxでは、あなたはただ行うことができます

objdump -d your_main_program > dump_file

于 2013-03-23T01:04:43.997 に答える