web-dev-qa-db-ja.com

C ++とCを組み合わせる - #ifdef __cplusplusはどのように機能するのですか?

私はたくさんの遺産を持ったプロジェクトに取り組んでいますCコード。私たちはC++で書き始めました。そして、やがてレガシーコードも変換するつもりです。 CとC++の相互作用について少し混乱しています。 Cコードをextern "C"でラップすることでC++コンパイラがCコードの名前をめちゃくちゃにすることはないことを私は理解していますが、これを実装する方法はまったくわかりません。

ですから、各(Cヘッダーファイルの先頭に(インクルードガードの後))、

#ifdef __cplusplus
extern "C" {
#endif

そして一番下に、書く

#ifdef __cplusplus
}
#endif

この2つの間には、すべてのインクルード、typedef、および関数プロトタイプがあります。これを正しく理解しているかどうかを確認するために、いくつか質問があります。

  1. CヘッダーファイルB.hを含み、別のCヘッダーファイルC.hを含むC++ファイルA.hhがある場合、これはどのように機能しますか?コンパイラーがB.hにステップインするとき、__cplusplusが定義されるので、extern "C"でコードをラップします(そして、__cplusplusはこのブロック内では定義されません)。そのため、C.hにステップインするとき、__cplusplusは定義されず、コードはextern "C"でラップされません。これは正しいです?

  2. extern "C" { extern "C" { .. } }でコードの一部をラップすることに問題がありますか? 2番目のextern "C"は何をしますか?

  3. このラッパーを.cファイルの周囲に配置せず、.hファイルだけに配置します。では、関数にプロトタイプがないとどうなりますか?コンパイラはC++関数だと思いますか。

  4. また、Cで記述されているサードパーティコードも使用しています。このようなラッパーはありません。そのライブラリのヘッダをインクルードするたびに、#includeの周りにextern "C"を付けています。これはそれに対処するための正しい方法ですか?

  5. 最後に、これは良い考えですか?他にやるべきことはありますか。近い将来CとC++を混在させるつもりです。私たちがすべての基盤をカバーしていることを確認したいと思います。

282
dublev

extern "C"は、コンパイラがコードを読み取る方法を実際には変更しません。あなたのコードが.cファイルの中にあればCとしてコンパイルされ、それが.cppファイルの中にあればそれはC++としてコンパイルされます(あなたの設定に奇妙なことをしない限り)。

extern "C"がすることはリンケージに影響を与えます。 C++関数は、コンパイル時に名前が壊れています - これがオーバーロードを可能にするものです。関数名はパラメータの種類と数に基づいて変更されるため、同じ名前の2つの関数は異なるシンボル名を持ちます。

extern "C"内のコードはまだC++コードです。 extern "C"ブロックでできることには制限がありますが、それらはすべてリンケージに関するものです。 Cリンケージでは構築できない新しいシンボルを定義することはできません。たとえば、クラスやテンプレートはありません。

extern "C"ブロックはうまく入れ子になっています。あなたがextern "C++"地域の中に絶望的に閉じ込められているのを見つけた場合もextern "C"がありますが、それは清潔さの観点からそれほど良い考えではありません。

さて、具体的にあなたの番号付きの質問に関して:

#1に関して:__cplusplusはextern "C"ブロック内で定義されたままになります。ただし、ブロックはきちんとネストする必要があるため、これは問題になりません。

#2に関して:__cplusplusは、C++コンパイラを介して実行されているコンパイル単位に対して定義されます。一般に、それは.cppファイルとその.cppファイルに含まれているファイルを意味します。異なるコンパイル単位が含まれている場合、同じ.h(または.hhまたは.hppまたはwhat-have-you)が異なる時点でCまたはC++として解釈される可能性があります。 .hファイル内のプロトタイプがCのシンボル名を参照するようにしたい場合は、C++として解釈されるときはextern "C"を持つ必要があり、Cとして解釈されるときはextern "C"を持つべきではありません - したがって#ifdef __cplusplusチェック。

質問3に答えると、プロトタイプのない関数は、.cppファイルの中にあり、extern "C"ブロックの中にない場合、C++リンケージを持つことになります。プロトタイプがない場合は、同じファイル内の他の関数からしか呼び出せず、リンケージがどのようなものかを気にする必要はありません。その関数を使用することを計画していないためです。とにかく、同じコンパイル単位の外側にあるものから呼ばれるべきです。

#4の場合、あなたはそれを正確に持っています。 Cリンケージを持つコード(Cコンパイラによってコンパイルされたコードなど)のヘッダーを含める場合は、ヘッダーをextern "C"する必要があります。そうすることで、ライブラリとリンクできるようになります。 (そうでなければ、void h(int, char)を探していたときに、リンカは_Z1hicのような名前の関数を探していました。

5:この種のミキシングはextern "C"を使用する一般的な理由であり、このようにしても何も問題はありません - 自分のしていることを理解していることを確認してください。

252
  1. extern "C"は、__cplusplusマクロの有無を変更しません。ラップされた宣言のリンケージと名前マングリングを変更するだけです。

  2. extern "C"ブロックをとても幸せにネストすることができます。

  3. .cファイルをC++としてコンパイルした場合、extern "C"ブロック内にはなく、extern "C"プロトタイプのないものはすべてC++関数として扱われます。あなたがそれらをCとしてコンパイルするなら、もちろんすべてはCの関数になるでしょう。

  4. はい

  5. このようにして、CとC++を安全に混在させることができます。

37

Andrew Shelanskyの優れた答えとに少し同意しないという共著であるいくつかの問題点は、コンパイラがコードを読み取る方法を実際には変えません

あなたの関数プロトタイプはCとしてコンパイルされるので、あなたは異なるパラメータで同じ関数名を多重定義することはできません - それはコンパイラの名前マングルの重要な特徴の一つです。これはリンケージの問題として説明されていますが、それは正しくありません - コンパイラとリンカの両方からエラーが発生します。

オーバーロードなどのプロトタイプ宣言のC++機能を使用しようとすると、コンパイラエラーが発生します。

関数が見つからないように見えるため、後でリンカエラーが発生します。notextern "C"宣言を囲むラッパーとヘッダーがCとC++のソースの混合に含まれています。

人々がCをC++としてコンパイルするのをやめさせる理由の一つは、これが彼らのソースコードがもう移植性がないことを意味するからです。その設定はプロジェクト設定なので、.cファイルが別のプロジェクトにドロップされても、c ++としてコンパイルされません。私はむしろ人々がファイルの接尾辞を.cppに改名するために時間をかけたいと思います。

20
Andy Dent

CとC++の両方のアプリケーションがCインタフェースを問題なく使用できるようにするためです。

C言語はとても簡単なので、GCC、Borland C\C++、MSVCなどのさまざまなコンパイラでコード生成は長年にわたって安定していました。

C++がますます普及する一方で、新しいC++ドメインには多くのことを追加する必要があります(たとえば、Cが必要とするすべての機能をカバーできなかったため、CfrontはAT&Tで放棄されました)。 template機能、コンパイル時のコード生成など、過去とは異なり、異なるコンパイラベンダーは実際にC++コンパイラとリンカの実際の実装を別々に行っていますが、実際のABIはC++とまったく互換性がありません。異なるプラットフォームでプログラムします。

実際のプログラムをC++で実装することを好むかもしれませんが、それでも古いCインタフェースとABIを通常どおりに保持し、ヘッダファイルで宣言する必要がありますextern "C" {}、コンパイラに伝えますインターフェース関数用に互換性のある/古い/単純な/簡単なC ABIを生成するコンパイラがC++コンパイラではなくCコンパイラである場合。

2
Bo Zhou