Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 19255] ファイルの概要

このコミットは、Go言語のCgoツールチェインの一部であるsrc/cmd/cgo/out.goファイルに対する変更です。out.goは、CgoがGoコードとCコード間の相互運用に必要なCヘッダーファイルやCソースファイルを生成する際に使用されるロジックを含んでいます。具体的には、Go関数をCから呼び出せるようにエクスポートする際のシンボル名の扱いに焦点を当てた修正が行われています。

コミット

commit fee51f45ab37c77ad8b7967b091ddf19d4e259a3
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Apr 29 08:53:38 2014 -0400

    cmd/cgo: for gccgo add #define to cgo_export.h for expected name
    
    For gccgo we rename exported functions so that the compiler
    will make them visible.  This CL adds a #define so that C
    functions that #include "cgo_export.h" can use the expected
    names of the function.
    
    The test for this is the existing issue6833 test in
    misc/cgo/test.  Without this CL it fails when using
    -compiler=gccgo.
    
    LGTM=minux.ma, rsc
    R=golang-codereviews, gobot, rsc, minux.ma
    CC=golang-codereviews
    https://golang.org/cl/91830046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/fee51f45ab37c77ad8b7967b091ddf19d4e259a3

元コミット内容

cmd/cgo: for gccgo add #define to cgo_export.h for expected name

gccgoの場合、エクスポートされた関数をコンパイラが可視化するように名前を変更します。この変更は、cgo_export.hをインクルードするC関数が、期待される関数名を使用できるように、#defineを追加するものです。

この変更のテストは、misc/cgo/testにある既存のissue6833テストです。この変更がない場合、-compiler=gccgoを使用するとテストが失敗します。

変更の背景

このコミットは、Go言語のCgoツールとgccgoコンパイラを使用する際の特定の互換性問題に対処するために導入されました。

Go言語には、C言語のコードとGo言語のコードを相互に呼び出すためのcgoというツールがあります。cgoは、GoのパッケージからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりすることを可能にします。この相互運用性を実現するために、cgoはGoとCの間のブリッジとなるコードを生成します。

Goコンパイラには、公式のgcコンパイラと、GCCフロントエンドをベースにしたgccgoコンパイラの2種類があります。gccgoは、GoコードをGCCのバックエンドを通じてコンパイルするため、生成されるバイナリやシンボルの扱いにgcコンパイラとは異なる特性を持つことがあります。

問題は、Goの関数をCから呼び出せるようにエクスポートする際に発生しました。cgoは通常、エクスポートされるGo関数に対して、Cコードから参照するための特定のシンボル名を生成します。しかし、gccgoコンパイラは、これらのエクスポートされたGo関数のシンボル名を内部的に変更(マングル)することがありました。これにより、Cコードがcgo_export.hヘッダーファイルを通じて期待する関数名でGo関数を呼び出そうとしても、gccgoが生成した実際のシンボル名と一致せず、リンケージエラーが発生していました。

具体的には、gccgoはエクスポートされたGo関数をデフォルトでstaticとして扱い、外部から参照できないようにしてしまう傾向がありました。これを回避するために、cgogccgo向けに__asm__ディレクティブを使用して、Go関数のシンボル名をCgoexp_プレフィックスを付けた形式に強制的に変更していました。しかし、この変更された名前はCコードが直接使用するものではなく、Cコードは元の(Cgoexp_プレフィックスのない)期待される名前でGo関数を参照しようとします。

この不一致が、misc/cgo/testにあるissue6833テストの失敗として現れていました。このテストは、Cgoを介してGo関数をCから呼び出すシナリオを検証するものであり、gccgoコンパイラを使用した場合にリンケージエラーにより失敗していました。

このコミットは、Cコードが期待する関数名とgccgoが生成する実際のシンボル名の間のギャップを埋めることで、この問題を解決することを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  1. Cgo:

    • Go言語とC言語のコードを相互運用するためのGoのツールです。
    • Goプログラム内でCの関数を呼び出したり、Cプログラム内でGoの関数を呼び出したりすることを可能にします。
    • import "C"という特殊な擬似パッケージを使用することで、GoコードからCの関数や変数にアクセスできます。
    • Cgoは、GoとCの間の呼び出し規約の変換、データ型のマッピング、および必要なブリッジコードの生成を行います。
    • Go関数をCから呼び出せるようにするには、Go関数を//export FunctionNameというコメントでマークし、cgo_export.hというヘッダーファイルを生成します。
  2. gccgo:

    • Go言語の代替コンパイラの一つで、GCC (GNU Compiler Collection) のフロントエンドとして実装されています。
    • GoのソースコードをGCCの中間表現に変換し、GCCの最適化パスとバックエンドを利用して実行可能なバイナリを生成します。
    • 公式のgcコンパイラとは異なるコード生成戦略やシンボル管理の特性を持つことがあります。特に、シンボルの可視性(エクスポートされるか否か)の扱いに違いが生じることがあります。
  3. シンボルとリンケージ:

    • コンパイルされたプログラムにおいて、関数やグローバル変数などは「シンボル」として表現されます。
    • シンボルは、その名前と、それがメモリ上のどこにあるかを示すアドレスを持ちます。
    • 「リンケージ」とは、異なるコンパイル単位(例えば、GoのオブジェクトファイルとCのオブジェクトファイル)で定義されたシンボルを解決し、それらを結合して最終的な実行可能ファイルを生成するプロセスです。
    • シンボルには「外部リンケージ」(他のファイルから参照可能)と「内部リンケージ」(そのファイル内でのみ参照可能、staticキーワードで指定されることが多い)があります。
    • gccgoは、Goからエクスポートされた関数をデフォルトで内部リンケージとして扱う傾向があり、これがCコードからの呼び出しを妨げる原因となっていました。
  4. cgo_export.h:

    • CgoがGo関数をCから呼び出せるようにエクスポートする際に生成するヘッダーファイルです。
    • このファイルには、エクスポートされたGo関数のC言語でのプロトタイプ宣言が含まれており、CコードはこのヘッダーをインクルードすることでGo関数を型安全に呼び出すことができます。
  5. #defineプリプロセッサディレクティブ:

    • C/C++言語のプリプロセッサ機能の一つで、マクロを定義するために使用されます。
    • #define MACRO_NAME replacement_textの形式で記述され、コンパイル前にMACRO_NAMEが出現する箇所がreplacement_textに置き換えられます。
    • このコミットでは、Cコードが期待する関数名とgccgoが生成する実際のシンボル名の間のマッピングを提供するために使用されます。
  6. __asm__ディレクティブ:

    • GCC拡張機能の一つで、C/C++コード内でアセンブリ言語を直接記述したり、シンボル名を操作したりするために使用されます。
    • このコミットでは、__asm__("symbol.name")の形式で、Go関数がCから参照される際のシンボル名を強制的に指定するために使われています。gccgoは、Goの関数名をそのままエクスポートするのではなく、gccgoの内部的な命名規則に従ってシンボル名を生成します。__asm__ディレクティブは、このgccgoが生成するシンボル名(例: _cgo_export.Cgoexp_MyGoFunction)を明示的に指定するために使用されます。

技術的詳細

このコミットの核心は、gccgoコンパイラがGoからエクスポートされた関数のシンボル名をどのように扱うか、という問題にあります。

通常のGoコンパイラ(gc)では、//export MyGoFunctionとマークされたGo関数は、Cコードから直接MyGoFunctionという名前で呼び出せるように、適切なシンボル名でエクスポートされます。

しかし、gccgoの場合、Goの関数がCから呼び出されるようにエクスポートされる際、gccgoはデフォルトでそのシンボルをstatic(内部リンケージ)として扱い、外部から参照できないようにしてしまう傾向があります。これを回避し、シンボルを外部に公開するために、cgogccgo向けに特別な処理を行います。

具体的には、cgoはエクスポートされるGo関数に対して、Cコードが期待する元の名前(例: MyGoFunction)とは異なる、Cgoexp_プレフィックスが付加された名前(例: Cgoexp_MyGoFunction)を内部的に生成します。そして、このCgoexp_プレフィックス付きの名前を、__asm__ディレクティブを使って、gccgoが生成する実際のシンボル名(例: _cgo_export.Cgoexp_MyGoFunction)にマッピングします。これにより、gccgoはシンボルを外部にエクスポートし、リンカが参照できるようになります。

問題は、Cコードがcgo_export.hをインクルードしてGo関数を呼び出す際に、Cgoexp_MyGoFunctionのような内部的な名前ではなく、元の期待される名前MyGoFunctionを使用しようとすることです。この名前の不一致がリンケージエラーを引き起こしていました。

このコミットは、この問題を解決するために、cgo_export.h#defineディレクティブを追加します。この#defineは、Cコードが期待する元の関数名(例: MyGoFunction)を、gccgoが実際にエクスポートするCgoexp_プレフィックス付きの名前(例: Cgoexp_MyGoFunction)にマッピングします。

例: #define MyGoFunction Cgoexp_MyGoFunction

これにより、CコードがMyGoFunction()と記述しても、プリプロセッサがそれをCgoexp_MyGoFunction()に展開するため、gccgoがエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。

さらに、_cgo_export.cというファイル(Cgoが生成するCソースファイルで、Go関数をCから呼び出すためのラッパー関数が定義されている)では、この#defineが問題を引き起こす可能性があります。なぜなら、_cgo_export.cは実際にCgoexp_MyGoFunctionという名前の関数を定義する必要があるため、#defineによってその名前がMyGoFunctionに置き換えられてしまうと、定義が正しく行われなくなるからです。このため、_cgo_export.cの先頭で、該当する#define#undefすることで無効化しています。これにより、_cgo_export.c内では元のCgoexp_MyGoFunctionという名前で関数を定義し、cgo_export.hをインクルードする他のCファイルでは#defineによる名前解決が行われる、という両立が実現されます。

この修正により、gccgoを使用した場合でも、Cgoを介したGo関数のエクスポートとCからの呼び出しが正しく機能するようになります。

コアとなるコードの変更箇所

変更はsrc/cmd/cgo/out.goファイルに集中しています。

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -880,10 +880,24 @@ func (p *Package) writeGccgoExports(fgo2, fc, fm *os.File) {
 		fmt.Fprintf(cdeclBuf, ")")
 		cParams := cdeclBuf.String()
 
+		// We need to use a name that will be exported by the
+		// Go code; otherwise gccgo will make it static and we
+		// will not be able to link against it from the C
+		// code.
 		goName := "Cgoexp_" + exp.ExpName
 		fmt.Fprintf(fgcch, `extern %s %s %s __asm__(\"%s.%s\");`, cRet, goName, cParams, gccgoSymbolPrefix, goName)
 		fmt.Fprint(fgcch, "\n")
 
+		// Use a #define so that the C code that includes
+		// cgo_export.h will be able to refer to the Go
+		// function using the expected name.
+		fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
+
+		// Use a #undef in _cgo_export.c so that we ignore the
+		// #define from cgo_export.h, since here we are
+		// defining the real function.
+		fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
+
 		fmt.Fprint(fgcc, "\n")
 		fmt.Fprintf(fgcc, "%s %s %s {\\n", cRet, exp.ExpName, cParams)
 		fmt.Fprint(fgcc, "\t")

追加された行は以下の通りです。

  1. fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
  2. fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)

コアとなるコードの解説

src/cmd/cgo/out.gowriteGccgoExports関数は、gccgoコンパイラ向けにGo関数をエクスポートするためのCヘッダーファイル(cgo_export.h)とCソースファイル(_cgo_export.c)を生成する役割を担っています。

変更前のコードでは、gccgoがGo関数を外部に公開できるように、Cgoexp_プレフィックスを付けた名前(goName)を生成し、__asm__ディレクティブを使ってそのシンボル名を明示的に指定していました。これにより、gccgoはシンボルをstaticにせず、外部リンケージとして扱えるようになります。

しかし、このgoNameはCコードが直接参照する名前ではありませんでした。Cコードはcgo_export.hをインクルードし、Go関数を元の名前(exp.ExpName)で呼び出そうとします。このギャップを埋めるのが今回の変更です。

  1. fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)

    • この行は、cgo_export.hファイル(fgcchcgo_export.hへのファイルポインタ)に#defineディレクティブを書き込みます。
    • exp.ExpNameは、Cコードが期待するGo関数の元の名前(例: MyGoFunction)です。
    • goNameは、gccgoが実際にエクスポートする、Cgoexp_プレフィックスが付加された名前(例: Cgoexp_MyGoFunction)です。
    • 結果として、cgo_export.hには例えば#define MyGoFunction Cgoexp_MyGoFunctionのような行が追加されます。
    • これにより、cgo_export.hをインクルードするCソースファイルでは、MyGoFunctionという名前がプリプロセッサによってCgoexp_MyGoFunctionに置き換えられ、gccgoがエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。
  2. fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)

    • この行は、_cgo_export.cファイル(fgcc_cgo_export.cへのファイルポインタ)に#undefディレクティブを書き込みます。
    • _cgo_export.cは、Go関数をCから呼び出すための実際のラッパー関数を定義するファイルです。このファイル内では、Cgoexp_MyGoFunctionという名前で関数を定義する必要があります。
    • もし_cgo_export.ccgo_export.hをインクルードし、かつこの#undefがない場合、#define MyGoFunction Cgoexp_MyGoFunctionによってCgoexp_MyGoFunctionという名前がMyGoFunctionに置き換えられてしまい、関数定義が正しく行えなくなります。
    • #undefを使用することで、_cgo_export.c内では、cgo_export.hで定義された#defineの影響を受けずに、元のCgoexp_プレフィックス付きの名前で関数を定義できるようになります。これは、_cgo_export.cが「実際の関数を定義する場所」であり、他のCファイルが「その関数を参照する場所」であるという役割の違いを考慮したものです。

これらの変更により、gccgoコンパイラを使用した場合でも、Cgoを介してGo関数がCコードから正しく参照され、リンケージエラーが解消されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語公式ドキュメント: Cgo (https://go.dev/blog/c-go-cgo)
  • GCC __asm__ keyword: (https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html)
  • Cプリプロセッサ #define#undef: (https://ja.cppreference.com/w/c/preprocessor/define)
  • Go言語のgccgoコンパイラに関する情報 (https://go.dev/doc/install/gccgo)
  • シンボルとリンケージに関する一般的な情報 (コンパイラ、リンカの概念)```markdown

[インデックス 19255] ファイルの概要

このコミットは、Go言語のCgoツールチェインの一部であるsrc/cmd/cgo/out.goファイルに対する変更です。out.goは、CgoがGoコードとCコード間の相互運用に必要なCヘッダーファイルやCソースファイルを生成する際に使用されるロジックを含んでいます。具体的には、Go関数をCから呼び出せるようにエクスポートする際のシンボル名の扱いに焦点を当てた修正が行われています。

コミット

commit fee51f45ab37c77ad8b7967b091ddf19d4e259a3
Author: Ian Lance Taylor <iant@golang.org>
Date:   Tue Apr 29 08:53:38 2014 -0400

    cmd/cgo: for gccgo add #define to cgo_export.h for expected name
    
    For gccgo we rename exported functions so that the compiler
    will make them visible.  This CL adds a #define so that C
    functions that #include "cgo_export.h" can use the expected
    names of the function.
    
    The test for this is the existing issue6833 test in
    misc/cgo/test.  Without this CL it fails when using
    -compiler=gccgo.
    
    LGTM=minux.ma, rsc
    R=golang-codereviews, gobot, rsc, minux.ma
    CC=golang-codereviews
    https://golang.org/cl/91830046

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/fee51f45ab37c77ad8b7967b091ddf19d4e259a3

元コミット内容

cmd/cgo: for gccgo add #define to cgo_export.h for expected name

gccgoの場合、エクスポートされた関数をコンパイラが可視化するように名前を変更します。この変更は、cgo_export.hをインクルードするC関数が、期待される関数名を使用できるように、#defineを追加するものです。

この変更のテストは、misc/cgo/testにある既存のissue6833テストです。この変更がない場合、-compiler=gccgoを使用するとテストが失敗します。

変更の背景

このコミットは、Go言語のCgoツールとgccgoコンパイラを使用する際の特定の互換性問題に対処するために導入されました。

Go言語には、C言語のコードとGo言語のコードを相互に呼び出すためのcgoというツールがあります。cgoは、GoのパッケージからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりすることを可能にします。この相互運用性を実現するために、cgoはGoとCの間のブリッジとなるコードを生成します。

Goコンパイラには、公式のgcコンパイラと、GCCフロントエンドをベースにしたgccgoコンパイラの2種類があります。gccgoは、GoコードをGCCのバックエンドを通じてコンパイルするため、生成されるバイナリやシンボルの扱いにgcコンパイラとは異なる特性を持つことがあります。

問題は、Goの関数をCから呼び出せるようにエクスポートする際に発生しました。cgoは通常、エクスポートされるGo関数に対して、Cコードから参照するための特定のシンボル名を生成します。しかし、gccgoコンパイラは、これらのエクスポートされたGo関数のシンボル名を内部的に変更(マングル)することがありました。これにより、Cコードがcgo_export.hヘッダーファイルを通じて期待する関数名でGo関数を呼び出そうとしても、gccgoが生成した実際のシンボル名と一致せず、リンケージエラーが発生していました。

具体的には、gccgoはエクスポートされたGo関数をデフォルトでstaticとして扱い、外部から参照できないようにしてしまう傾向がありました。これを回避するために、cgogccgo向けに__asm__ディレクティブを使用して、Go関数のシンボル名をCgoexp_プレフィックスを付けた形式に強制的に変更していました。しかし、この変更された名前はCコードが直接使用するものではなく、Cコードは元の(Cgoexp_プレフィックスのない)期待される名前でGo関数を参照しようとします。

この不一致が、misc/cgo/testにあるissue6833テストの失敗として現れていました。このテストは、Cgoを介してGo関数をCから呼び出すシナリオを検証するものであり、gccgoコンパイラを使用した場合にリンケージエラーにより失敗していました。

このコミットは、Cコードが期待する関数名とgccgoが生成する実際のシンボル名の間のギャップを埋めることで、この問題を解決することを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念について理解しておく必要があります。

  1. Cgo:

    • Go言語とC言語のコードを相互運用するためのGoのツールです。
    • Goプログラム内でCの関数を呼び出したり、Cプログラム内でGoの関数を呼び出したりすることを可能にします。
    • import "C"という特殊な擬似パッケージを使用することで、GoコードからCの関数や変数にアクセスできます。
    • Cgoは、GoとCの間の呼び出し規約の変換、データ型のマッピング、および必要なブリッジコードの生成を行います。
    • Go関数をCから呼び出せるようにするには、Go関数を//export FunctionNameというコメントでマークし、cgo_export.hというヘッダーファイルを生成します。
  2. gccgo:

    • Go言語の代替コンパイラの一つで、GCC (GNU Compiler Collection) のフロントエンドとして実装されています。
    • GoのソースコードをGCCの中間表現に変換し、GCCの最適化パスとバックエンドを利用して実行可能なバイナリを生成します。
    • 公式のgcコンパイラとは異なるコード生成戦略やシンボル管理の特性を持つことがあります。特に、シンボルの可視性(エクスポートされるか否か)の扱いに違いが生じることがあります。
  3. シンボルとリンケージ:

    • コンパイルされたプログラムにおいて、関数やグローバル変数などは「シンボル」として表現されます。
    • シンボルは、その名前と、それがメモリ上のどこにあるかを示すアドレスを持ちます。
    • 「リンケージ」とは、異なるコンパイル単位(例えば、GoのオブジェクトファイルとCのオブジェクトファイル)で定義されたシンボルを解決し、それらを結合して最終的な実行可能ファイルを生成するプロセスです。
    • シンボルには「外部リンケージ」(他のファイルから参照可能)と「内部リンケージ」(そのファイル内でのみ参照可能、staticキーワードで指定されることが多い)があります。
    • gccgoは、Goからエクスポートされた関数をデフォルトで内部リンケージとして扱う傾向があり、これがCコードからの呼び出しを妨げる原因となっていました。
  4. cgo_export.h:

    • CgoがGo関数をCから呼び出せるようにエクスポートする際に生成するヘッダーファイルです。
    • このファイルには、エクスポートされたGo関数のC言語でのプロトタイプ宣言が含まれており、CコードはこのヘッダーをインクルードすることでGo関数を型安全に呼び出すことができます。
  5. #defineプリプロセッサディレクティブ:

    • C/C++言語のプリプロセッサ機能の一つで、マクロを定義するために使用されます。
    • #define MACRO_NAME replacement_textの形式で記述され、コンパイル前にMACRO_NAMEが出現する箇所がreplacement_textに置き換えられます。
    • このコミットでは、Cコードが期待する関数名とgccgoが生成する実際のシンボル名の間のマッピングを提供するために使用されます。
  6. __asm__ディレクティブ:

    • GCC拡張機能の一つで、C/C++コード内でアセンブリ言語を直接記述したり、シンボル名を操作したりするために使用されます。
    • このコミットでは、__asm__("symbol.name")の形式で、Go関数がCから参照される際のシンボル名を強制的に指定するために使われています。gccgoは、Goの関数名をそのままエクスポートするのではなく、gccgoの内部的な命名規則に従ってシンボル名を生成します。__asm__ディレクティブは、このgccgoが生成するシンボル名(例: _cgo_export.Cgoexp_MyGoFunction)を明示的に指定するために使用されます。

技術的詳細

このコミットの核心は、gccgoコンパイラがGoからエクスポートされた関数のシンボル名をどのように扱うか、という問題にあります。

通常のGoコンパイラ(gc)では、//export MyGoFunctionとマークされたGo関数は、Cコードから直接MyGoFunctionという名前で呼び出せるように、適切なシンボル名でエクスポートされます。

しかし、gccgoの場合、Goの関数がCから呼び出されるようにエクスポートされる際、gccgoはデフォルトでそのシンボルをstatic(内部リンケージ)として扱い、外部から参照できないようにしてしまう傾向があります。これを回避し、シンボルを外部に公開するために、cgogccgo向けに特別な処理を行います。

具体的には、cgoはエクスポートされるGo関数に対して、Cコードが期待する元の名前(例: MyGoFunction)とは異なる、Cgoexp_プレフィックスが付加された名前(例: Cgoexp_MyGoFunction)を内部的に生成します。そして、このCgoexp_プレフィックス付きの名前を、__asm__ディレクティブを使って、gccgoが生成する実際のシンボル名(例: _cgo_export.Cgoexp_MyGoFunction)にマッピングします。これにより、gccgoはシンボルを外部にエクスポートし、リンカが参照できるようになります。

問題は、Cコードがcgo_export.hをインクルードしてGo関数を呼び出す際に、Cgoexp_MyGoFunctionのような内部的な名前ではなく、元の期待される名前MyGoFunctionを使用しようとすることです。この名前の不一致がリンケージエラーを引き起こしていました。

このコミットは、この問題を解決するために、cgo_export.h#defineディレクティブを追加します。この#defineは、Cコードが期待する元の関数名(例: MyGoFunction)を、gccgoが実際にエクスポートするCgoexp_プレフィックス付きの名前(例: Cgoexp_MyGoFunction)にマッピングします。

例: #define MyGoFunction Cgoexp_MyGoFunction

これにより、CコードがMyGoFunction()と記述しても、プリプロセッサがそれをCgoexp_MyGoFunction()に展開するため、gccgoがエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。

さらに、_cgo_export.cというファイル(Cgoが生成するCソースファイルで、Go関数をCから呼び出すためのラッパー関数が定義されている)では、この#defineが問題を引き起こす可能性があります。なぜなら、_cgo_export.cは実際にCgoexp_MyGoFunctionという名前の関数を定義する必要があるため、#defineによってその名前がMyGoFunctionに置き換えられてしまうと、定義が正しく行われなくなるからです。このため、_cgo_export.cの先頭で、該当する#define#undefすることで無効化しています。これにより、_cgo_export.c内では元のCgoexp_MyGoFunctionという名前で関数を定義し、cgo_export.hをインクルードする他のCファイルでは#defineによる名前解決が行われる、という両立が実現されます。

この修正により、gccgoを使用した場合でも、Cgoを介したGo関数のエクスポートとCからの呼び出しが正しく機能するようになります。

コアとなるコードの変更箇所

変更はsrc/cmd/cgo/out.goファイルに集中しています。

--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -880,10 +880,24 @@ func (p *Package) writeGccgoExports(fgo2, fc, fm *os.File) {
 		fmt.Fprintf(cdeclBuf, ")")
 		cParams := cdeclBuf.String()
 
+		// We need to use a name that will be exported by the
+		// Go code; otherwise gccgo will make it static and we
+		// will not be able to link against it from the C
+		// code.
 		goName := "Cgoexp_" + exp.ExpName
 		fmt.Fprintf(fgcch, `extern %s %s %s __asm__(\"%s.%s\");`, cRet, goName, cParams, gccgoSymbolPrefix, goName)
 		fmt.Fprint(fgcch, "\n")
 
+		// Use a #define so that the C code that includes
+		// cgo_export.h will be able to refer to the Go
+		// function using the expected name.
+		fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
+
+		// Use a #undef in _cgo_export.c so that we ignore the
+		// #define from cgo_export.h, since here we are
+		// defining the real function.
+		fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)
+
 		fmt.Fprint(fgcc, "\n")
 		fmt.Fprintf(fgcc, "%s %s %s {\\n", cRet, exp.ExpName, cParams)
 		fmt.Fprint(fgcc, "\t")

追加された行は以下の通りです。

  1. fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)
  2. fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)

コアとなるコードの解説

src/cmd/cgo/out.gowriteGccgoExports関数は、gccgoコンパイラ向けにGo関数をエクスポートするためのCヘッダーファイル(cgo_export.h)とCソースファイル(_cgo_export.c)を生成する役割を担っています。

変更前のコードでは、gccgoがGo関数を外部に公開できるように、Cgoexp_プレフィックスを付けた名前(goName)を生成し、__asm__ディレクティブを使ってそのシンボル名を明示的に指定していました。これにより、gccgoはシンボルをstaticにせず、外部リンケージとして扱えるようになります。

しかし、このgoNameはCコードが直接参照する名前ではありませんでした。Cコードはcgo_export.hをインクルードし、Go関数を元の名前(exp.ExpName)で呼び出そうとします。このギャップを埋めるのが今回の変更です。

  1. fmt.Fprintf(fgcch, "#define %s %s\\n", exp.ExpName, goName)

    • この行は、cgo_export.hファイル(fgcchcgo_export.hへのファイルポインタ)に#defineディレクティブを書き込みます。
    • exp.ExpNameは、Cコードが期待するGo関数の元の名前(例: MyGoFunction)です。
    • goNameは、gccgoが実際にエクスポートする、Cgoexp_プレフィックスが付加された名前(例: Cgoexp_MyGoFunction)です。
    • 結果として、cgo_export.hには例えば#define MyGoFunction Cgoexp_MyGoFunctionのような行が追加されます。
    • これにより、cgo_export.hをインクルードするCソースファイルでは、MyGoFunctionという名前がプリプロセッサによってCgoexp_MyGoFunctionに置き換えられ、gccgoがエクスポートした正しいシンボル名でGo関数を呼び出すことができるようになります。
  2. fmt.Fprintf(fgcc, "#undef %s\\n", exp.ExpName)

    • この行は、_cgo_export.cファイル(fgcc_cgo_export.cへのファイルポインタ)に#undefディレクティブを書き込みます。
    • _cgo_export.cは、Go関数をCから呼び出すための実際のラッパー関数を定義するファイルです。このファイル内では、Cgoexp_MyGoFunctionという名前で関数を定義する必要があります。
    • もし_cgo_export.ccgo_export.hをインクルードし、かつこの#undefがない場合、#define MyGoFunction Cgoexp_MyGoFunctionによってCgoexp_MyGoFunctionという名前がMyGoFunctionに置き換えられてしまい、関数定義が正しく行えなくなります。
    • #undefを使用することで、_cgo_export.c内では、cgo_export.hで定義された#defineの影響を受けずに、元のCgoexp_プレフィックス付きの名前で関数を定義できるようになります。これは、_cgo_export.cが「実際の関数を定義する場所」であり、他のCファイルが「その関数を参照する場所」であるという役割の違いを考慮したものです。

これらの変更により、gccgoコンパイラを使用した場合でも、Cgoを介してGo関数がCコードから正しく参照され、リンケージエラーが解消されるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語公式ドキュメント: Cgo (https://go.dev/blog/c-go-cgo)
  • GCC __asm__ keyword: (https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html)
  • Cプリプロセッサ #define#undef: (https://ja.cppreference.com/w/c/preprocessor/define)
  • Go言語のgccgoコンパイラに関する情報 (https://go.dev/doc/install/gccgo)
  • シンボルとリンケージに関する一般的な情報 (コンパイラ、リンカの概念)