CではできないのにC ++ではヘッダーファイル内にメソッド定義があるのはなぜですか?


23

Cでは、ヘッダーファイル内に関数の定義/実装を含めることはできません。ただし、C ++では、ヘッダーファイル内でメソッドを完全に実装できます。なぜ振る舞いが違うのですか?

回答:


28

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用語です。)


2
これはそれほど悪い 考えではありません-この種のものはGNU Libcヘッダーでも見つけることができます。
SKロジック

しかし、条件付きコンパイルディレクティブでのヘッダーファイルの慣用的なラッピングはどうでしょうか。次に、ヘッダーで定義および定義された関数を使用しても、一度だけロードされます。私はCが初めてなので、誤解しているかもしれません。
-user305964

2
@papiro問題は、ラッピングがコンパイラの1回の実行中にのみ保護することです。したがって、foo.cが1回の実行でfoo.oにコンパイルされ、bar.cが別の実行でbar.oにコンパイルされ、foo.oとbar.oが3回(通常)でa.outにリンクされる場合、そのラッピング各オブジェクトファイルに1つずつ、複数のインスタンスを防止しません。
デビッドコンラッド

ここで説明されている問題#ifndef HEADER_Hは、予防すべきものではありませんか?
ロバートハーベイ

27

この点で、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();
ルスラン

6

ヘッダーファイルの概念には、少し説明が必要です。

コンパイラのコマンドラインでファイルを指定するか、「#include」を実行します。ほとんどのコンパイラは、拡張子がc、C、cpp、c ++などのコマンドファイルをソースファイルとして受け入れます。ただし、通常、ソースファイルへの任意の拡張子の使用を有効にするコマンドラインオプションが含まれています。

通常、コマンドラインで指定されるファイルは「ソース」と呼ばれ、含まれるファイルは「ヘッダー」と呼ばれます。

プリプロセッサのステップは実際にそれらすべてを取得し、コンパイラにとってすべてが単一の大きなファイルのように見えるようにします。ヘッダーまたはソースにあったものは、この時点では実際には関係ありません。通常、このステージの出力を表示できるコンパイラのオプションがあります。

そのため、コンパイラーのコマンドラインで指定された各ファイルに対して、巨大なファイルがコンパイラーに指定されます。これには、メモリを占有したり、他のファイルから参照されるシンボルを作成したりするコード/データが含まれる場合があります。これで、それぞれが「オブジェクト」画像を生成します。リンカは、リンクされている3つ以上のオブジェクトファイルで同じシンボルが見つかった場合、「重複シンボル」を提供できます。おそらくこれが理由です。オブジェクトファイルにシンボルを作成できるヘッダーファイルにコードを配置することはお勧めしません。

通常、「インライン」はインライン化されますが、デバッグ時にはインライン化されない場合があります。では、なぜリンカは多重定義されたエラーを出さないのですか?シンプル...これらは「弱い」シンボルであり、すべてのオブジェクトからの弱いシンボルのすべてのデータ/コードが同じサイズとコンテンツである限り、リンクは1つのコピーを保持し、他のオブジェクトからコピーをドロップします。できます。


3

C99でこれを行うことができます。inline関数は別の場所で提供されることが保証されているため、関数がインライン化されなかった場合、その定義は宣言に変換されます(つまり、実装は破棄されます)。そして、もちろん、を使用できますstatic


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"シンボルは、弱いオブジェクトシンボルとして特にタグ付けされていない弱いシンボルです。弱い定義シンボルが通常の定義シンボルとリンクされている場合、通常の定義シンボルはエラーなしで使用されます。弱い未定義のシンボルがリンクされ、シンボルが定義されていない場合、シンボルの値はエラーなしにシステム固有の方法で決定されます。一部のシステムでは、大文字はデフォルト値が指定されていることを示します。


-4

おそらく、Javaのクラス定義内に完全なメソッド実装を配置しなければならないのと同じ理由で。

かっこで囲まれ、多くの同じキーワードを使用して、似ているかもしれませんが、言語は異なります。


1
いいえ、そこに本当の答えは実際にはあり、そしてC ++の主な目標の一つはC.との下位互換性のあることだった
エド・S.

4
いいえ、「高度なC互換性」と「Cとの不適合な互換性がない」ように設計されています。(両方ともStroustrupから)。この特定の非互換性が無償ではない理由を強調するために、より詳細な答えを出すことができることに同意します。自由に提供してください。
ポール・ブッチャー

持っていただろうが、サイモン・リヒターはそれを投稿したときにすでに持っていた。「下位互換性」と「高度なC互換性」の違いについてはすることができますが、この答えが間違っているという事実は残っています。C#とC ++を比較した場合、最後のステートメントは正しいでしょうが、CとC ++とはそれほど違いません。
エドS.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.