C関数を名前変換できないのはなぜですか?


136

私は最近インタビューを受けました、そして尋ねられた一つの質問はextern "C"C ++コードで何が使用されているかということでした。Cは名前のマングリングを使用しないので、C ++コードでC関数を使用することだと答えました。Cが名前のマングリングを使用しない理由を尋ねられましたが、正直に答えることができませんでした。

C ++コンパイラーが関数をコンパイルすると、主にコンパイル時に解決する必要があるC ++で同じ名前のオーバーロードされた関数を使用できるため、関数に特別な名前が付けられることを理解しています。Cでは、関数の名前は同じままか、おそらくその前に_が付いています。

私のクエリは:C ++コンパイラがC関数を壊すことを許可することの何が問題になっていますか?コンパイラがそれらに付ける名前は問題ではないと私は思いました。CおよびC ++でも同じように関数を呼び出します。


75
Cには関数のオーバーロードがないため、名前をマングルする必要ありません。
EOF

9
C ++コンパイラが関数名をマングルする場合、CライブラリとC ++コードをどのようにリンクしますか?
マット

6
「Cは名前のマングリングを使用しないため、C ++コードでC関数を使用することだと答えました。」-逆だと思います。Extern "C"は、C ++関数をCコンパイラで使用できるようにします。出典
rozina

3
@ Engineer999:また、C ++コンパイラでもC ++であるCのサブセットをコンパイルすると、関数名は実際に破損します。しかし、異なるコンパイラで作成されたバイナリをリンクできるようにしたい場合は、名前のマングリングは必要ありません。
EOF

13
C 名前をマングルします。通常、マングル名は、アンダースコアが前に付いた関数の名前です。時々、それはアンダースコアが続く関数の名前です。extern "C""the" Cコンパイラと同じ方法で名前を壊すように言います。
ピートベッカー

回答:


187

上記のように答えられましたが、状況を把握するようにします。

まず、Cが最初に来ました。したがって、Cが行うことは、一種の「デフォルト」です。名前をマングルしないので、名前をマングルしません。関数名は関数名です。グローバルはグローバルなどです。

その後、C ++が登場しました。C ++は、Cと同じリンカーを使用し、Cで記述されたコードとリンクできるようにしたいと考えていました。しかし、C ++は、Cを「マングリング」(またはその欠如)のままにすることはできませんでした。次の例を確認してください。

int function(int a);
int function();

C ++では、これらは個別の本体を持つ個別の関数です。それらのいずれもマングルされていない場合、両方とも「関数」(または「_function」)と呼ばれ、リンカはシンボルの再定義について文句を言うでしょう。C ++の解決策は、引数の型を関数名に変換することでした。したがって、1つが呼び出され、もう1つが呼び出さ_function_int_function_void(実際のマングリング方式ではない)、衝突が回避されます。

今、私たちは問題を抱えています。場合はint function(int a)Cモジュールで定義された、と我々は単にC ++コードにそのヘッダ(すなわち宣言)を取り、それを使っていると、コンパイラは輸入にリンカに指示を生成します_function_int。関数が定義されたとき、Cモジュールではそれは呼び出されませんでした。と呼ばれました_function。これにより、リンカエラーが発生します。

このエラーを回避するために、関数の宣言中に、Cコンパイラとリンクするか、Cコンパイラでコンパイルするように設計された関数であることをコンパイラに通知します。

extern "C" int function(int a);

C ++コンパイラー_function_function_int、ではなくインポートすることを認識し、すべて順調です。


1
@ShacharShamesh:私は他の場所でこれを尋ねましたが、C ++コンパイル済みライブラリでのリンクについてはどうですか?コンパイラーがC ++コンパイル済みライブラリーの関数の1つを呼び出す私のコードをステップ実行してコンパイルしているとき、宣言または関数呼び出しを見ただけで、どの名前をマングルするか、または関数に与えるかをどのようにして知るのですか?それが定義されている場所で、それが他の名前にマングルされていることを知る方法は?それでは、C ++には標準の名前変換メソッドが必要ですか?
Engineer999

2
すべてのコンパイラは、独自の特別な方法でそれを行います。同じコンパイラーですべてをコンパイルする場合、それは問題ではありません。しかし、たとえば、Microsoftのコンパイラを使用して構築しているプログラムから、Borlandのコンパイラを使用してコンパイルされたライブラリを使用しようとすると、幸運です。あなたはそれが必要になるでしょう:)
Mark VY

6
@ Engineer999移植可能なC ++ライブラリなどがないのかと思ったことがありますが、使用するコンパイラ(および標準ライブラリ)のバージョン(およびフラグ)を正確に指定するか、C APIをエクスポートするだけですか?どうぞ。C ++は、これまでに開発された移植性の最も低い言語ですが、Cはその逆です。そこその点での努力はしているが、あなたは本当にポータブル何かをしたい場合は、今のあなたはC.に固執う
VOO

1
@Vooええと、理論的には標準に準拠するだけで移植可能なコードを記述できるはず-std=c++11です。これは、Javaバージョンの宣言と同じです(ただし、新しいJavaバージョンには下位互換性があります)。コンパイラ固有の拡張機能とプラットフォームに依存するコードを使用するのは、標準の誤りではありません。一方、規格には多くのものが(ソケットのようなIOなど)不足しているため、非難することはできません。委員会はそれにゆっくりと追いついているようです。何かを逃した場合は私を修正してください。
mucaho

14
@mucaho:あなたはソースの移植性/互換性について話しています。つまり、API。Vooは、再コンパイルせずにバイナリ互換性について話している。これにはABI互換性が必要です。C ++コンパイラは、バージョン間で定期的にABIを変更します。(たとえば、g ++は安定したABIを作ろうとはしません。楽しみのためだけにABIを壊すことはないと思いますが、何かを得る必要がある場合にABIの変更を必要とする変更を避けず、他に良い方法はありません。それをするために。)。
Peter Cordes

45

彼らが「できない」というわけはなく、一般的にそうはありません

と呼ばれるCライブラリの関数を呼び出したい場合foo(int x, const char *y)、C ++コンパイラがそれをfoo_I_cCP()(または、ここではその場でマングリングスキームを作成しただけで)マングルできるようにしても、できません。

その名前は解決されません。関数はCにあり、その名前は引数の型のリストに依存しません。したがって、C ++コンパイラはこれを認識し、マングリングを回避するためにその関数をCとしてマークする必要があります。

上記のC関数は、ソースコードのないライブラリに含まれている可能性があることを覚えておいてください。したがって、C ++コンパイラは「それ自体」のものを実行できません。結局、ライブラリの内容を変更することはできません。


これが欠けている部分です。なぜC ++コンパイラは、宣言が見つかるか、または呼び出されているのを見たときに、関数名をマングルするのでしょうか。それはそれらの実装を見たときに、単に関数名をマングルしませんか?これは私にはもっと理にかなっています
Engineer999

13
@ Engineer999:定義の名前と宣言の名前をどのように持つことができますか?「呼び出すことができるBrianという関数があります。」「よし、ブライアンに電話するよ」「申し訳ありませんが、ブライアンという機能はありません。」それはグラハムと呼ばれることがわかりました。
Orbitのライトネスレース2016年

C ++コンパイル済みライブラリでのリンクについてはどうですか?コンパイラーがC ++コンパイル済みライブラリーの関数の1つを呼び出すコードをステップ実行してコンパイルするとき、宣言または関数呼び出しを見ただけで、どの名前をマングルまたは関数に与えるかをどのようにして知るのですか?
Engineer999

1
@ Engineer999両方が同じマングリングに同意する必要があります。したがって、彼らはヘッダーファイル(ネイティブDLLにはメタデータがほとんどないことを思い出してください-ヘッダーはそのメタデータです)を見て、「ああ、そうです、ブライアンは本当にグラハムであるはずです」。これが機能しない場合(たとえば、2つの互換性のないマングリング方式を使用している場合)、正しいリンクが取得されず、アプリケーションが失敗します。C ++には、このような非互換性がたくさんあります。実際には、マングルされた名前を明示的に使用して、マングルを無効にする必要があります(たとえば、コードにブライアンではなくグラハムを実行するように指示します)。では、実際の練習... extern "C":)
Luaan

1
@ Engineer999私は間違っているかもしれませんが、おそらくVisual Basic、C#、Java(または、Pascal / Delphi)のような言語の経験はありますか?これらは相互運用を非常に単純に見せます。C、特にC ++では、それ以外のものです。DLL自体には十分な情報が含まれていないため(特に、純粋なC。ヘッダーファイルがない場合は、通常、DLLを逆コンパイルして使用する必要があります。
Luaan

32

C ++コンパイラがC関数を壊すのを許可することの何が問題になっていますか?

それらはもはやC関数ではありません。

関数は単なるシグネチャや定義ではありません。関数がどのように機能するかは、主に呼び出し規約などの要因によって決まります。プラットフォームで使用するために指定された「アプリケーションバイナリインターフェース」は、システムが相互に通信する方法を説明します。システムで使用されているC ++ ABIは名前マングリング方式を指定しているため、そのシステム上のプログラムは、ライブラリなどの関数を呼び出す方法を認識しています。(すばらしい例については、C ++ Itanium ABIを読んでください。なぜそれが必要なのかがすぐにわかります。)

システムのC ABIにも同じことが当てはまります。一部のC ABIには実際に名前マングリングスキーム(Visual Studioなど)があるため、特定の関数については、「名前マングリングをオフにする」よりも、C ++ ABIからC ABIに切り替えることについて説明します。C関数は、C ABI(C ++ ABIではなく)が適切なC関数であるとマークします。宣言は定義と一致する必要があります(同じプロジェクト内か、一部のサードパーティライブラリ内のいずれかである場合)。そうでない場合、宣言は無意味です。それがなければ、あなたのシステムはそれらの関数を見つけて呼び出す方法を知らないだけです。

プラットフォームがCとC ++のABIを同じであると定義せずにこの「問題」を取り除く理由については、それは部分的に歴史的です—元のC ABIは、名前空間、クラス、および演算子のオーバーロードがあるC ++には十分ではありませんでした。なにかコンピュータにやさしい方法でシンボルの名前で表す必要がありますが、CプログラムをC ++に準拠させることは、Cコミュニティでは不公平であると主張する人もいるかもしれません。 ABIは、相互運用性を必要とする他の一部の人々のためにだけあります。


2
+int(PI/3)しかし、塩の一粒で:私は、「C ++ ABI」の話をするのは非常に慎重になるだろう...私の知る限り、ある試み C ++のABIを定義する時、ない本当の デファクト / デジュール標準は、 -としてisocpp.org/files /papers/n4028.pdfは(そして私も心から同意します)述べていますが、C ++がextern“ Cを介してC ++のCサブセットに頼ることにより、安定したバイナリABIでAPIを公開する方法を常にサポートしていることは非常に皮肉です」。C++ Itanium ABIそれだけです

3
@vaxquis:ええ、「C ++のABI」ではなく、「C ++ ABI」は、すべての家で機能しない「家の鍵」を持っているのと同じ方法で。私は、フレーズをオフに開始することで、可能な限り明確なようにそれを作ってみましたけれども、それは、より明確になることができると思います「C ++ ABI システムで使用されています。簡潔にするために後の発話で明確化子を削除しましたが、ここでは混乱を減らす編集を受け入れます!
オービットのライトネスレース2016年

1
AIUI C abiはプラットフォームのプロパティである傾向があり、C ++ ABIは個々のコンパイラのプロパティである傾向があり、多くの場合、コンパイラの個々のバージョンのプロパティでさえあります。したがって、異なるベンダーのツールで構築されたモジュール間をリンクしたい場合は、インターフェースにC abiを使用する必要がありました。
プラグウォッシュ

「名前がマングルされた関数はC関数ではなくなる」というステートメントは誇張されています。マングルされた名前がわかっている場合は、プレーンなバニラCから名前がマングルされた関数を呼び出すことは完全に可能です。名前が変更されても、C ABIへの準拠が低下することはありません。つまり、C関数の機能が低下することはありません。逆の方法のほうが理にかなっています。C++コードは、Cを宣言しないとC関数を呼び出すことができませんでした
ピーター-モニカの復活2016年

@ PeterA.Schneider:はい、見出しのフレーズは誇張されています。答えの全体の残りの部分は、関連事実の詳細が含まれています。
Orbitのライトネスレース2016年

21

MSVCは、単純な方法ですが、実際に C名を壊します。それは時々追加する@4か、または別の小さな数字です。これは、呼び出し規約とスタックのクリーンアップの必要性に関連しています。

したがって、前提には欠陥があります。


2
それは本当にマングリングという名前ではありません。これは、異なる呼び出し規約を持つ関数で構築されたDLLにリンクされた実行可能ファイルの問題を防ぐためのベンダー固有の命名(または名前の装飾)規約です。
Peter

2
先頭にaを付けるのは_どうですか?
OrangeDog

12
@ピーター:文字通り同じこと。
オービットのライトネスレース2016年

5
@Frankie_C:「呼び出し元がスタックをクリーンアップする」は、どのC標準でも指定されていません。言語の観点から見ると、どちらの呼び出し規則も他の標準よりも標準的ではありません。
Ben Voigt 2016

2
そして、MSVCの観点からは、「標準の呼び出し規約」はまさにあなたが選ぶものです /Gd, /Gr, /Gv, /Gz。(つまり、関数宣言で呼び出し規約が明示的に指定されていない限り、標準の呼び出し規約が使用されます。)あなたは__cdeclどちらがデフォルトの標準の呼び出し規約であるかを考えています。
MSalters

13

部分的にCで記述され、部分的に他の言語(多くの場合、アセンブリ言語ですが、Pascal、FORTRAN、またはその他)で記述されたプログラムを持つことは非常に一般的です。また、すべてのソースコードを持っているわけではないさまざまな人々によって作成されたさまざまなコンポーネントがプログラムに含まれていることもよくあります。

ほとんどのプラットフォームには、ABI [Application Binary Interface]と呼ばれる仕様があります。これは、特定の型の引数を受け入れ、特定の型の値を返す特定の名前の関数を生成するためにコンパイラーが実行する必要があることを記述します。場合によっては、ABIが複数の「呼び出し規約」を定義することがあります。そのようなシステムのコンパイラは、特定の関数に使用する呼び出し規約を示す手段を提供することがよくあります。たとえば、Macintoshでは、ほとんどのツールボックスルーチンがPascal呼び出し規約を使用するため、「LineTo」などのプロトタイプは次のようになります。

/* Note that there are no underscores before the "pascal" keyword because
   the Toolbox was written in the early 1980s, before the Standard and its
   underscore convention were published */
pascal void LineTo(short x, short y);

プロジェクト内のすべてのコードが同じコンパイラを使用してコンパイルされた場合、コンパイラが各関数にエクスポートした名前は問題ではありませんが、多くの状況では、Cコードが他のツールを使用してコンパイルされた関数を呼び出す必要があり、現在のコンパイラで再コンパイルすることはできません[C言語で作成されていない可能性もあります]。したがって、そのような関数を使用するには、リンカー名を定義できることが重要です。


はい、それが答えです。CとC ++だけの場合、なぜそのように行われるのかを理解するのは困難です。理解するには、静的リンクの古い方法のコンテキストに物事を置かなければなりません。静的リンクはWindowsプログラマにとっては原始的なようですが、C 名前をマングルできない主な理由です。
user34660 2016

2
@ user34660:正解ではありません。これが、Cが実装にエクスポート可能な名前のマ​​ングル化、または2次的な特性によって区別される複数の同じような名前のシンボルの存在を許可する必要がある機能の存在を強制できない理由です。
スーパーキャット2016

そのようなことを「義務付ける」試みがあったこと、またはそのようなことはC ++より前のCで利用可能な拡張であることを知っていますか?
user34660

@ user34660:「静的リンクはWindowsプログラマーにとって原始的なようです...」ですが、動的リンクはLinuxを使用している人々にとって主要なPITAのように見える場合があります。システムにすでに異なるバージョンのライブラリがある場合。
jamesqf

@jamesqf、はい、UnixにはWindowsより前の動的リンクはありませんでした。Unix / Linuxでの動的リンクについてはほとんど知りませんが、オペレーティングシステムで一般的にできるほどシームレスではないようです。
user34660

12

私が行った接線方向の議論のいくつかに対処するために、もう1つの答えを追加します。

C ABI(アプリケーションバイナリインターフェイス)は、元々スタックに引数を逆の順序で渡すことを求めていました(つまり、右から左にプッシュされました)。この場合、呼び出し元もスタックストレージを解放します。現代のABIは実際には引数を渡すためにレジスターを使用しますが、マングル化の考慮事項の多くは、元のスタック引数の受け渡しに戻ります。

対照的に、元のPascal ABIは引数を左から右にプッシュし、呼び出し先は引数をポップする必要がありました。オリジナルのC ABIは、2つの重要な点でオリジナルのPascal ABIより優れています。引数のプッシュ順序は、最初の引数のスタックオフセットが常にわかっていることを意味し、未知の数の引数を持つ関数を許可します。初期の引数は、他の引数の数を制御します(ala printf)。

C ABIが優れている2番目の方法は、呼び出し元と呼び出し先が引数の数に同意しない場合の動作です。Cの場合、実際に最後の引数を過ぎて引数にアクセスしない限り、何も悪いことは起こりません。Pascalでは、誤った数の引数がスタックからポップされ、スタック全体が破損します。

オリジナルのWindows 3.1 ABIはPascalに基づいていました。そのため、Pascal ABI(左から右の順序の引数、呼び出し先ポップ)を使用しました。引数番号の不一致はスタックの破損につながる可能性があるため、マングリング方式が形成されました。各関数名は、その引数のサイズ(バイト単位)を示す数値でマングルされました。したがって、16ビットマシンでは、次の関数(C構文)を使用します。

int function(int a)

は2バイト幅なfunction@2ので、にマングルされましたint。これは、宣言と定義が一致しない場合に、リンカが実行時にスタックを破壊するのではなく、関数を見つけられないようにするために行われました。逆に、プログラムがリンクしている場合は、呼び出しの最後にスタックから正しいバイト数が確実にポップされます。

32ビットWindows以降では、stdcall代わりにABIを使用します。これはPascal ABIに似ていますが、Cの場合と同様に、右から左へのプッシュ順です。Pascal ABIのように、名前のマングリングは、引数のバイトサイズを関数名に変換して、スタックの破損を回避します。

ここの他の場所で行われた主張とは異なり、C ABIはVisual Studioであっても関数名を壊しません。逆に、stdcallABI仕様で装飾されたマングリング関数は、VSに固有のものではありません。Linux用にコンパイルする場合でも、GCCはこのABIもサポートします。これは、独自のローダーを使用してLinuxコンパイル済みバイナリをWindowsコンパイル済みDLLに実行時にリンクできるようにするWineで広く使用されています。


9

C ++コンパイラは、名前の変換を使用して、他の方法ではシグネチャが同じになるオーバーロードされた関数に一意のシンボル名を許可します。基本的には、引数のタイプもエンコードします。これにより、関数ベースのレベルでのポリモーフィズムが可能になります。

Cは関数のオーバーロードを許可しないため、これを必要としません。

名前のマングリングは 'C ++ ABI'に依存できない理由の1つ(ただし、唯一ではない!)であることに注意してください。


8

C ++は、それに対してリンクする、またはそれに対してリンクするCコードと相互運用できることを望んでいます。

Cは、名前がマングルされていない関数名を想定しています。

C ++がそれをマングルした場合、Cからエクスポートされたマングルされていない関数が見つからないか、CがC ++がエクスポートした関数が見つかりません。Cリンカは、それがC ++から来ているのかC ++に向かっているのかわからないため、Cリンカはそれ自体が期待する名前を取得する必要があります。


3

C関数と変数の名前をマングリングすると、リンク時にそれらの型をチェックできます。現在、すべての(?)C実装では、1つのファイルで変数を定義し、別のファイルで関数として呼び出すことができます。または、間違ったシグネチャを使用して関数を宣言することもできます(たとえばvoid fopen(double)、それを呼び出します)。

私は1991年にマングルを使用してCの変数と関数型保証された形でリンクするスキームを提案ました。このスキームは採用されたことはありません。


1
リンク時に型をチェックできるようにする」という意味です。タイプコンパイル時にチェックされますが、マングルされていない名前とリンクすると、異なるコンパイル単位で使用されている宣言が一致するかどうかをチェックできません。そして、彼らが同意しない場合、根本的に壊れており、修正する必要があるのはあなたのビルドシステムです。
cmaster-モニカを2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.