40

(c) ANSI ISO/IEC 14882:2003、127 ページによると:

リンケージ仕様のネスト。リンケージ仕様がネストすると、最も内側の仕様が言語を決定します。リンケージ仕様は有効範囲を確立しません。リンケージ仕様は、名前空間スコープ (3.3) でのみ発生するものとします。リンケージ仕様では、指定された言語リンケージは、宣言によって導入されたすべての関数宣言子、関数名、および変数名の関数型に適用されます。

extern "C" void f1(void(*pf)(int));
// the name f1 and its function type have C language
// linkage; pf is a pointer to a C function

extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the
// function's type has C language linkage

extern "C" FUNC f3;
// the name of function f3 and the function's type
// have C language linkage

void (*pf2)(FUNC*);
// the name of the variable pf2 has C++ linkage and
// the type of pf2 is pointer to C++ function that
// takes one parameter of type pointer to C function

これはどういう意味ですか?たとえば、f2()関数にはどのようなリンケージがありますか? C または C++ 言語のリンケージですか?

@Johannes Schaub が指摘したように、これが標準で何を意味するかについての実際の説明はないため、異なるコンパイラでは異なる解釈が可能です。

オブジェクトファイルの違いを説明してください:

  • C言語リンケージとC++言語リンケージを持つ関数の名前。
  • C 言語リンケージおよび C++ 言語リンケージを持つ関数の型。
4

7 に答える 7

18

C++言語リンケージは、とnon-C++コード フラグメント間のリンケージに使用される用語です。通常、C++ プログラムでは、すべての関数名、関数型、さらには変数名にもデフォルトの C++ 言語リンケージがあります。

CC++ オブジェクト コードは、定義済みのリンケージ指定子を使用して、他のソース言語 ( など) を使用して生成された別のオブジェクト コードにリンクできます。

の概念を認識している必要があるためname mangling、関数名、関数型、および変数名をエンコードして、それらに一意の名前を生成します。これにより、リンカーは一般的な名前を区別できます (関数のオーバーロードの場合と同様)。C モジュールを C++ コンパイラでコンパイルされたライブラリまたはオブジェクト ファイルとリンクする場合、名前マングリングは望ましくありません。このような場合の名前マングリングを防ぐために、リンケージ指定子が使用されます。この場合、extern "C"はリンケージ指定子です。例を見てみましょう (ここで言及されている C++ コード):

typedef int (*pfun)(int);  // line 1
extern "C" void foo(pfun); // line 2
extern "C" int g(int)      // line 3
...
foo( g ); // Error!        // line 5

pfun行 1では、リンケージ指定子がないため、C++ 関数を指すように宣言しています。

したがって、2 行目では、foo が C++ 関数へのポインターを受け取る C 関数であることを宣言しています。

5 行目では、型が一致しない C 関数である g へのポインターを使用して foo を呼び出そうとしています。

関数名のリンケージの違い:

2 つの異なるファイルを見てみましょう。

リンケージのあるものextern "c"(file1.cpp):

#include <iostream>
using namespace std;

extern "C"
{
void foo (int a, int b)
{
    cout << "here";
}
}

int main ()
{
    foo (10,20);
    return 0;
}

リンクのないextern "c"もの (file2.cpp):

#include <iostream>
using namespace std;

void foo (int a, int b)
{
    cout << "here";
}

int main ()
{
    foo (10,20);
    return 0;
}

これら 2 つをコンパイルして、objdump を確認します。

# g++ file1.cpp -o file1
# objdump -Dx file1

# g++ file2.cpp -o file2
# objdump -Dx file2

extern "C" リンケージでは、関数の名前マングリングはありませんfoo。したがって、それを使用しているすべてのプログラム (それから共有ライブラリを作成すると仮定) は、名前マングリングの影響を考慮せずに(dlsymおよびのようなヘルパー関数を使用して) foo を直接呼び出すことができます。dlopen

0000000000400774 <foo>:
  400774:   55                      push   %rbp
  400775:   48 89 e5                mov    %rsp,%rbp
....
....
  400791:   c9                      leaveq 
  400792:   c3                      retq   

0000000000400793 <main>:
  400793:   55                      push   %rbp
  400794:   48 89 e5                mov    %rsp,%rbp
  400797:   be 14 00 00 00          mov    $0x14,%esi
  40079c:   bf 0a 00 00 00          mov    $0xa,%edi
  4007a1:   e8 ce ff ff ff          callq  400774 <foo>
  4007a6:   b8 00 00 00 00          mov    $0x0,%eax
  4007ab:   c9                      leaveq 

一方、 noextern "C"が使用されている場合、 func:fooはいくつかの事前定義されたルール (使用されているコンパイラ/リンカーに知られている) でマングルされるため、アプリケーションは名前を として指定して直接呼び出すことができませんfoo_Z3fooiiただし、必要に応じてマングル名 (この場合) で呼び出すこともできますが、明らかな理由で誰も使用しません。

0000000000400774 <_Z3fooii>:
  400774:   55                      push   %rbp
  400775:   48 89 e5                mov    %rsp,%rbp
 ...
...
  400791:   c9                      leaveq 
  400792:   c3                      retq   

0000000000400793 <main>:
  400793:   55                      push   %rbp
  400794:   48 89 e5                mov    %rsp,%rbp
  400797:   be 14 00 00 00          mov    $0x14,%esi
  40079c:   bf 0a 00 00 00          mov    $0xa,%edi
  4007a1:   e8 ce ff ff ff          callq  400774 <_Z3fooii>
  4007a6:   b8 00 00 00 00          mov    $0x0,%eax
  4007ab:   c9                      leaveq 
  4007ac:   c3                      retq   

このページは、この特定のトピックについても読むのに適しています。

呼び出し規約に関するわかりやすく明確に説明された記事: http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

于 2011-05-19T14:56:28.443 に答える
2

「名前 f2 には C++ 言語リンケージがあります」 C++ 言語リンケージでは、関数の名前だけでなく、その引数の型と戻り値も定義されます。この場合、次のようになります。 void f2(void); ただし、次のように定義できます。 void f2(int a); リンケージはそれらを異なる型として見るため、競合することはありません。これは、C 言語ではできないことです。

「関数の型がC言語リンケージを持っている」 詳細はわかりませんが、高次元でわかります。基本的に、C++ でコンパイルされた関数を C からリンク可能にします。私の記憶が正しければ、C と C++ では、パラメーターが関数に渡される方法が異なります。この場合、関数 f2 は、C コンパイラがこれを行うようにパラメーターを渡します。このようにして、関数は C と C++ の両方からリンク可能になります。

于 2011-05-18T08:22:41.250 に答える
2
extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the
// function's type has C language linkage

名前は1行目に書いてあるFUNCので「C」リンケージで宣言しています。extern "C"

f2C++ リンケージがデフォルトであるため、この名前には C++ リンケージが含まれており、2 行目に他のリンケージは指定されていません。

名前が C リンケージを持つ関数を参照するために使用されるという事実は、名前f2リンケージを変更しません。

于 2011-05-19T15:21:40.340 に答える
2

これはどういう意味ですか?たとえば、f2() 関数にはどのようなリンケージがありますか? C または C++ 言語のリンケージですか?

extern "C" typedef void FUNC();
FUNC f2;
// the name f2 has C++ language linkage and the 
// function's type has C language linkage 

「f2() 関数」と呼んでいるものには、そのリンケージに 2 つの側面があります。

  • シンボル テーブル (C++ 言語リンケージを持つ) でのその名前のマングリングの有無、および
  • 関数を呼び出す場合に必要な C または C++ 呼び出し規約 (C)。

呼び出すf2()には、オブジェクトファイルにその名前別名シンボルを見つけます。これは、「引数を取らない f2 という名前の関数」のマングルバージョンになります。上記のコードをコンパイルしてオブジェクトを検査することで、これを簡単に確認できます (例: GNU ツールを使用nm --demangle)。

ただし、関数を呼び出すには、事前条件と事後条件の再レジスター使用、スタック設定などの規則は C 関数のものです。C と C++ の関数が異なる呼び出し規則を持つことは合法であり、たとえば、C++ の例外処理を容易にするために行われる場合があります。

C言語連携の関数名とC++言語連携の関数名のオブジェクトファイルの違いを教えてください。

  • C リンケージの場合、"f2" は、次の結果のオブジェクト ファイル内のシンボルになります。f2()
  • C++ リンケージの場合、「引数を取らない f2 という名前の関数」のいくつかのマングル バージョン (GNU の場合、_Z2f2vこれは にデマングルしf2()ます)

C 言語リンケージおよび C++ 言語リンケージを持つ関数の型。

上記で説明したように、これは関数のアドレスでコードを呼び出すためのレジスタ/スタックの使用規則に関するものです。このメタ情報は、必ずしもオブジェクトのシンボル テーブル情報に格納されるとは限りません (もちろん、シンボル名キー自体の一部ではありません)。

さらに、各関数は呼び出し規約の 1 つを採用しているため、コンパイラーは、関数へのポインターをたどるときに使用する呼び出し規約を知る必要があります。その洞察により、問題の残りのコードが明確になると思います。

http://developers.sun.com/solaris/articles/mixing.htmlに優れた議論があります。特に、「関数へのポインターの操作」セクションをお勧めします。

于 2011-05-20T02:41:05.463 に答える
2

これは、プログラムのABI (Application Binary Interface)に関係しています。

API がプログラムのソース コードの外部インターフェイスを指定するように、ABI はプログラムのバイナリ コード(コンパイルされたバージョン) の外部インターフェイスを指定します。


もともと、C 関数にはいくつかの異なる形式がありました。何かのようなもの

int foo(int);

コンパイラによってアンダースコアがプレフィックスとして付けられ、 が形成され_fooエクスポートされて他のアプリケーションで使用できるようになります。

しかし、それだけでは不十分でした。たとえば、Windows API を見ると、次のようなことがわかります。

DWORD CreateWindowW(...);        //Original parameters
DWORD CreateWindowExW(..., ...); //More parameters

これは、関数の名前を見るだけでは関数のオーバーロードを区別する方法がないためです。そのため、人々はEx接尾辞 (など) を追加してそれらを変更し始めました。

これはかなり醜いものになり、C++ で機能していた演算子のオーバーロードはまだ許可されていませんでした。このため、C++ は名前マングリングを思いつきました。これは、パラメーターのデータ型などの追加情報を関数の名前に入れ、多くの@シンボルで暗号化したものにします。

完全に標準化されていないことを除けば、すべて順調でした。

もちろん、新しい言語やコンパイラが登場すると、それぞれ独自のスキームが生まれ、互換性のないものもありました。したがって、外部関数をインポートまたはエクスポートする必要がある場合は、コンパイラが検索する ABI の種類を指定する必要がextern "C++"あります。

于 2011-05-19T19:20:10.290 に答える
1

C / C ++でよく知られているように、コード変換は、コンパイルとリンクという2つの主要なフェーズで構成されています。コンパイラがオブジェクトファイルを生成すると、関数が呼び出または参照されるオブジェクトファイルを指定する情報がリンカに渡されます。Cでは、それはまさにそのようなものであり、関数には名前と一致する定義があります。

// file1.c
void foo(void) {}

そして、コンパイル後、file1.objは、fooシンボルの定義に関するコードと情報を格納します。

しかし、C ++が登場すると、シンボル名はより複雑になります。関数がオーバーロードされているか、クラスのメンバーである可能性があります。しかし、リンカはそれを知りたくありません。古いリンカーの単純さと再利用性を維持するには、fooが次のいずれであるかに関係なく単一の名前が必要です。

void foo(void) {}
void foo(int) {}
void ClassA::foo(void) {}

しかし、もはやfooとだけ呼ぶことはできないので、ここに名前マングリングがあります。また、コンパイラからfoo_void、foo_int、foo_void_classaなどのバリエーションを取得する場合があります。そして最後に、リンカーはすべての人が単純なシンボルのように見えるので満足しています。

C ++コードでCコンパイラーを使用してコンパイルされたfoo関数を呼び出す場合、C ++コンパイラーが想定するfoo_voidではなく、fooをCスタイルのfooにするようにコンパイラーに指示する必要があります。それは以下を使用して行われます:

extern "C" void foo();

これで、コンパイラはfooがCコンパイラを使用してコンパイルされていることを認識し、このコードがfooを呼び出すという情報をリンカに渡します。リンカはそれをfile1.objのfoo定義と照合します。だから私が思うのはそれだけです。

cdeclやstdcallなどの他のディレクティブはWindows固有であり、関数呼び出しのパラメーターがどのように渡されるかを示します。はい、CおよびC++の場合はcdeclです。ただし、Windows API関数はstdcall(Pascal規則)を使用します(単純で、歴史的にMicrosoftはPascalでWindows開発環境を提供していました)。

于 2011-05-19T15:03:09.060 に答える
0

すべての関数、関数型、およびオブジェクトには、単純な文字列として指定される言語リンケージがあります。デフォルトでは、リンケージは「C++」です。他の標準言語リンケージは「C」のみです。他のすべての言語リンケージと、さまざまな言語リンケージに関連付けられているプロパティは実装定義です。

于 2012-04-10T16:51:27.777 に答える