Cでは、ヘッダーファイル内に関数の定義/実装を含めることはできません。ただし、C ++では、ヘッダーファイル内でメソッドを完全に実装できます。なぜ振る舞いが違うのですか?
Cでは、ヘッダーファイル内に関数の定義/実装を含めることはできません。ただし、C ++では、ヘッダーファイル内でメソッドを完全に実装できます。なぜ振る舞いが違うのですか?
回答:
Cでは、ヘッダーファイルで関数を定義すると、そのヘッダーファイルを含むコンパイルされた各モジュールにその関数が表示され、その関数のパブリックシンボルがエクスポートされます。したがって、関数additupがheader.hで定義されており、foo.cとbar.cの両方にheader.hが含まれている場合、foo.oとbar.oの両方にadditupのコピーが含まれます。
これら2つのオブジェクトファイルをリンクすると、リンカーはシンボルadditupが複数回定義されていることを認識し、それを許可しません。
関数が静的であると宣言した場合、シンボルはエクスポートされません。オブジェクトファイルfoo.oとbar.oには両方とも関数のコードの別々のコピーが含まれており、それらを使用することはできますが、リンカーは関数のコピーを見ることができないため、文句を言うことはありません。もちろん、他のモジュールも関数を見ることができません。そして、あなたのプログラムは同じ関数の2つの同一のコピーで膨れ上がります。
ヘッダーファイルで関数を宣言するだけで、定義せずに1つのモジュールだけで定義すると、リンカーは関数のコピーを1つ表示し、プログラム内のすべてのモジュールがそれを表示し、これを使って。コンパイルされたプログラムには、関数のコピーが1つだけ含まれます。
したがって、Cのヘッダーファイルに関数定義を含めることができます。これは、スタイル、形式、および総合的な悪いアイデアにすぎません。
(「宣言」とは、本体なしで関数プロトタイプを提供することを意味します。「定義」とは、関数本体の実際のコードを提供することを意味します。これは標準のC用語です。)
#ifndef HEADER_H
は、予防すべきものではありませんか?
この点で、CとC ++の動作はほぼ同じですinline
。ヘッダーに関数を含めることができます。C ++では、本体がクラス定義内にあるメソッドはすべて暗黙的にinline
です。Cでも同じことをしたい場合は、関数を宣言しますstatic inline
。
static inline
」...そして、それを使用する各翻訳単位に関数の複数のコピーがまだあります。static
inline
機能なしのC ++では、コピーは1つしかありません。実際にCのヘッダーに実装を含めるには、1)実装をinline
(例inline void func(){do_something();}
)としてマークし、2)この関数が特定の翻訳単位(例:)にあると実際に言う必要がありますvoid func();
。
ヘッダーファイルの概念には、少し説明が必要です。
コンパイラのコマンドラインでファイルを指定するか、「#include」を実行します。ほとんどのコンパイラは、拡張子がc、C、cpp、c ++などのコマンドファイルをソースファイルとして受け入れます。ただし、通常、ソースファイルへの任意の拡張子の使用を有効にするコマンドラインオプションが含まれています。
通常、コマンドラインで指定されるファイルは「ソース」と呼ばれ、含まれるファイルは「ヘッダー」と呼ばれます。
プリプロセッサのステップは実際にそれらすべてを取得し、コンパイラにとってすべてが単一の大きなファイルのように見えるようにします。ヘッダーまたはソースにあったものは、この時点では実際には関係ありません。通常、このステージの出力を表示できるコンパイラのオプションがあります。
そのため、コンパイラーのコマンドラインで指定された各ファイルに対して、巨大なファイルがコンパイラーに指定されます。これには、メモリを占有したり、他のファイルから参照されるシンボルを作成したりするコード/データが含まれる場合があります。これで、それぞれが「オブジェクト」画像を生成します。リンカは、リンクされている3つ以上のオブジェクトファイルで同じシンボルが見つかった場合、「重複シンボル」を提供できます。おそらくこれが理由です。オブジェクトファイルにシンボルを作成できるヘッダーファイルにコードを配置することはお勧めしません。
通常、「インライン」はインライン化されますが、デバッグ時にはインライン化されない場合があります。では、なぜリンカは多重定義されたエラーを出さないのですか?シンプル...これらは「弱い」シンボルであり、すべてのオブジェクトからの弱いシンボルのすべてのデータ/コードが同じサイズとコンテンツである限り、リンクは1つのコピーを保持し、他のオブジェクトからコピーをドロップします。できます。
C ++標準引用
C ++ 17 N4659標準案 10.1.6「インライン指定子は、」方法が暗黙のうちに、インラインであることを述べています:
4クラス定義内で定義された関数はインライン関数です。
そしてさらに下に行くと、インラインメソッドはすべての翻訳単位で定義できるだけでなく、定義する必要があることがわかります。
6インライン関数またはインライン変数は、odrが使用されるすべての翻訳単位で定義され、すべての場合でまったく同じ定義を持つものとします(6.2)。
これは、12.2.1「メンバー関数」の注記にも明示的に記載されています。
1メンバー関数は、そのクラス定義で定義(11.4)できます。この場合、インラインメンバー関数(10.1.6)[...]
3 [注:プログラムには、非インラインメンバー関数の定義が1つしかありません。プログラムには複数のインラインメンバー関数定義が存在する場合があります。6.2および10.1.6を参照してください。—終了ノート]
GCC 8.3の実装
main.cpp
struct MyClass {
void myMethod() {}
};
int main() {
MyClass().myMethod();
}
シンボルのコンパイルと表示:
g++ -c main.cpp
nm -C main.o
出力:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
U __stack_chk_fail
0000000000000000 T main
次に、ELFオブジェクトファイルでシンボルが弱いとマークされman nm
ていることがわかりMyClass::myMethod
ます。これは、複数のオブジェクトファイルに表示される可能性があることを意味します。
"W" "w"シンボルは、弱いオブジェクトシンボルとして特にタグ付けされていない弱いシンボルです。弱い定義シンボルが通常の定義シンボルとリンクされている場合、通常の定義シンボルはエラーなしで使用されます。弱い未定義のシンボルがリンクされ、シンボルが定義されていない場合、シンボルの値はエラーなしにシステム固有の方法で決定されます。一部のシステムでは、大文字はデフォルト値が指定されていることを示します。
おそらく、Javaのクラス定義内に完全なメソッド実装を配置しなければならないのと同じ理由で。
かっこで囲まれ、多くの同じキーワードを使用して、似ているかもしれませんが、言語は異なります。