web-dev-qa-db-ja.com

なぜC関数は名前をマングルできないのですか?

最近インタビューを受けましたが、C++コードでextern "C"をどのように使用するかという質問がありました。 Cは名前のマングリングを使用しないため、C++コードでC関数を使用することだと答えました。私はなぜCが名前マングリングを使用しないのか尋ねられ、正直に言えば答えられませんでした。

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

私のクエリは次のとおりです。C++コンパイラがC関数もマングルできるようにすることの何が問題になっていますか?コンパイラーがどのような名前を付けても問題ないと思います。 CとC++でも同じ方法で関数を呼び出します。

135
Engineer999

上記のように答えられましたが、コンテキストに物事を入れようとします。

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

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

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

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

これで問題が残りました。 int function(int a)がCモジュールで定義されており、C++コードでそのヘッダー(つまり宣言)を取得して使用しているだけの場合、コンパイラは_function_intをインポートするようにリンカーに命令を生成します。 Cモジュールで関数が定義されたとき、それは呼び出されませんでした。 _functionと呼ばれていました。これにより、リンカーエラーが発生します。

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

extern "C" int function(int a);

C++コンパイラは、_functionではなく_function_intをインポートするようになり、すべて順調です。

189
Shachar Shemesh

一般的に「できない」ということではなく、ないです。

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

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

上記のC関数は、ソースコードを持たないライブラリ内にある可能性があります。あなたが持っているのは、プリコンパイルされたバイナリとヘッダーだけです。したがって、C++コンパイラは「独自のこと」を行うことができず、結局ライブラリの内容を変更することはできません。

45
unwind

c ++コンパイラがC関数もマングルできるようにすることの何が問題になっていますか?

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

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

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

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

実際、MSVC does Cの名前をマングルしますが、簡単な方法です。 @4または別の小さな数字を追加する場合があります。これは、呼び出し規約とスタッククリーンアップの必要性に関連しています。

そのため、前提には欠陥があります。

19
MSalters

プログラムの一部が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でさえないかもしれません]。そのため、このような関数を使用するには、リンカー名を定義できることが重要です。

13
supercat

行われた接線方向の議論のいくつかに対処するために、もう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)

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

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

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

12
Shachar Shemesh

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

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

名前のマングリングが「C++ ABI」に依存できない理由の1つであることに注意してください。

9
OnMyLittleDuck

C++は、リンクする、またはリンクするCコードと相互運用できるようにしたいと考えています。

Cでは、名前がマングルされていない関数名が必要です。

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

Cの関数と変数の名前をマングリングすると、リンク時にそれらの型を確認できます。現在、すべての(?)C実装では、あるファイルで変数を定義し、それを別のファイルで関数として呼び出すことができます。または、間違った署名(たとえば、void fopen(double))で関数を宣言してから呼び出すことができます。

1991年にマングリングを使用して、 C変数と関数のタイプセーフなリンケージのスキーム を提案しました。このスキームは採用されたことはありません。 。

3