[インデックス 14672] ファイルの概要
このコミットは、Go言語のcmd/cgo
ツールにおいて、C言語のvoid
型を返す関数からerrno
(エラー番号)にアクセスできるようにするための修正です。具体的には、C言語側でvoid
関数がerrno
を設定した場合に、Go言語側でそのerrno
値を正しく取得できるようにcgo
の内部処理を改善しています。これにより、C言語のAPIがvoid
を返しつつerrno
でエラーを示すようなケースでも、Go言語から適切にエラーハンドリングが可能になります。
コミット
- コミットハッシュ:
1b18a6072e3af427b92e26e7f472fd6d0c6efc09
- 作者: Shenghou Ma minux.ma@gmail.com
- コミット日時: 2012年12月18日 火曜日 00:26:08 +0800
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/1b18a6072e3af427b92e26e7f472fd6d0c6efc09
元コミット内容
cmd/cgo: access errno from void C function
Fixes #3729.
R=rsc
CC=golang-dev
https://golang.org/cl/6938052
変更の背景
この変更は、Go言語のIssue 3729「cmd/cgo
: access errno from void C function」を解決するために行われました。
Go言語のcgo
ツールは、GoプログラムからC言語のコードを呼び出すためのメカニズムを提供します。C言語の関数は、通常、エラーが発生した場合に特定の値を返すか、グローバル変数errno
を設定することでエラーを通知します。しかし、C言語には戻り値がvoid
(何も返さない)である関数も存在し、これらの関数がエラーを通知する唯一の手段としてerrno
を使用する場合があります。
従来のcgo
の実装では、void
を返すC関数をGoから呼び出した際に、その関数が設定したerrno
値をGo側で適切に取得できないという問題がありました。Goの慣習では、C関数呼び出しの結果として(result, error)
の2つの値を返すことが期待されます。void
関数はGoの世界では[0]byte
(空のバイト配列)とerror
を返すものとして扱われますが、errno
が正しくマッピングされないため、C側でエラーが発生してもGo側でそれを検知できないという不具合が生じていました。
このコミットは、このようなvoid
を返すC関数からのerrno
アクセスを可能にし、GoプログラムがCライブラリとのより堅牢な連携を行えるようにすることを目的としています。
前提知識の解説
cgo
cgo
は、Go言語のプログラムからC言語の関数を呼び出したり、C言語のコード内でGoの関数を使用したりするためのGoツールチェーンの一部です。cgo
を使用することで、既存のCライブラリをGoプロジェクトに統合したり、パフォーマンスが重要な部分をCで記述したりすることが可能になります。
cgo
は、Goのソースファイル内に特別なコメントブロック(import "C"
の直前)でCコードを記述することで機能します。cgo
はこれらのCコードをコンパイルし、GoとCの間でデータを変換し、関数呼び出しを仲介する接着コード(glue code)を生成します。
errno
errno
は、C言語の標準ライブラリで定義されているグローバル変数(またはスレッドローカル変数)です。システムコールやライブラリ関数がエラーを報告するために使用されます。関数が失敗した場合、errno
には特定のエラーを示す整数値が設定されます。例えば、EACCES
はアクセス拒否、ENOENT
はファイルやディレクトリが見つからないことを示します。
Go言語では、C関数がerrno
を設定した場合、cgo
はそれをGoのerror
インターフェースに変換して返します。これにより、Goの慣用的なエラーハンドリング(if err != nil
)でCのエラーを処理できます。
C言語のvoid
関数
C言語において、void
を戻り値の型として持つ関数は、呼び出し元に値を返さないことを意味します。これらの関数は、副作用(例:グローバル変数の変更、ファイルへの書き込み、画面への出力)を実行するために使用されます。
例えば、void print_message(const char *msg)
のような関数は、メッセージを画面に出力するだけで、呼び出し元に特定の値を返しません。しかし、このような関数でも内部でエラーが発生した場合、errno
を設定してエラーを通知することがあります。
Go言語におけるCエラーの扱い
cgo
は、C関数が返す値とerrno
をGoの戻り値にマッピングします。通常、C関数が単一の値を返す場合、Go側では(C.ReturnType, error)
のように2つの値を返します。error
はerrno
が設定されている場合に非nil
となります。void
関数については、Go側では(struct{}, error)
または(byte, error)
のように、何らかのダミー値とerror
を返すものとして扱われます。このコミットの修正前は、void
関数の場合にerrno
が正しくGoのerror
にマッピングされない問題がありました。
技術的詳細
このコミットの核心は、cgo
コンパイラ(src/cmd/cgo/gcc.go
)がC言語のvoid
型をどのように扱うかを変更し、void
関数からのerrno
アクセスを可能にすることです。
cgo
は、Cの型をGoの型にマッピングする際に、dwarf
(Debugging With Arbitrary Record Formats)情報を使用して型情報を解析します。これまでの実装では、dwarf.VoidType
がGoのvoid
(Goのvoid
は実際には存在しないが、cgo
内部で概念的に使用される)にマッピングされていました。しかし、このマッピングでは、void
関数がerrno
を設定した場合に、Go側でそのerrno
を適切に取得するためのメカニズムが不足していました。
修正前は、cgo
はvoid
関数からの戻り値がないため、call2
(2つの戻り値を持つ関数呼び出し、つまり(result, error)
形式)のコンテキストでvoid
関数が呼び出された場合、戻り値の数が合わないとしてエラーを報告していました。これは、void
関数がGoの世界でerror
を返す可能性があることを考慮していなかったためです。
このコミットでは、以下の主要な変更が導入されています。
_Ctype_void
の導入:src/cmd/cgo/gcc.go
のtypeConv
構造体にgoVoid ast.Expr
フィールドが追加され、_Ctype_void
という識別子に初期化されます。これは、Cのvoid
型をGoの世界で表現するための特別な型として機能します。これにより、cgo
はvoid
型を単なる「何もない」ものとしてではなく、errno
を伴う可能性のある戻り値のコンテキストで適切に扱えるようになります。dwarf.VoidType
のマッピング変更:typeConv.Type
メソッド内で、dwarf.VoidType
がGoのc.goVoid
(つまり_Ctype_void
)にマッピングされるようになりました。また、Align
が1
に設定されています。これは、void
型がメモリ上で1バイトのサイズを持つかのように扱われることを示唆しており、cgo
がvoid
関数からの戻り値を処理する際に、errno
を格納するためのスペースを確保するなどの内部的な調整を可能にします。FuncType
の戻り値処理の改善:typeConv.FuncType
メソッド内で、C関数の戻り値の型がdwarf.VoidType
である場合、Goの戻り値としてc.goVoid
を含むast.Field
が生成されるようになりました。これにより、void
関数がGoの世界で_Ctype_void
とerror
の2つの戻り値を持つかのように扱われ、errno
がGoのerror
に正しくマッピングされる道が開かれました。rewriteRef
の変更:src/cmd/cgo/gcc.go
のrewriteRef
関数から、void
関数がcall2
コンテキストで呼び出された場合に発生していた「assignment count mismatch」のエラーチェックが削除されました。これは、上記の変更によりvoid
関数がGoの世界で2つの戻り値を持つことが許容されるようになったため、このチェックが不要になったためです。
これらの変更により、cgo
はvoid
を返すC関数がerrno
を設定した場合でも、Go側でそのerrno
をerror
として取得できるようになります。
コアとなるコードの変更箇所
このコミットで変更された主要なファイルは以下の通りです。
misc/cgo/test/cgo_test.go
: 新しいテストケースTest3729
が追加され、issue3729.go
で定義されたテスト関数が実行されるように更新されました。misc/cgo/test/issue3729.go
(新規):void
を返すC関数g
と、引数を持つvoid
関数g2
を定義し、それぞれがerrno
を設定するテストケースです。Go側でC.g()
とC.g2()
を呼び出し、返されたerror
が期待されるerrno
値(syscall.E2BIG
やsyscall.EINVAL
)と一致するかを検証します。このファイルはWindows以外のシステム向けです。misc/cgo/test/issue3729w.go
(新規):issue3729.go
と同様のテストですが、Windowsシステム向けです。Windowsではerrno
の動作が異なるため、このテストはスキップされます。misc/cgo/test/issue4417.go
: このファイルは、このコミットとは直接関係のない変更で、ファイルの先頭にあった// run
コメントが削除されました。これはおそらく、テスト実行方法の変更に伴うクリーンアップです。src/cmd/cgo/gcc.go
:cgo
コンパイラの核心部分であり、このコミットの主要なロジック変更が含まれています。
コアとなるコードの解説
src/cmd/cgo/gcc.go
の変更
このファイルはcgo
コンパイラのバックエンドの一部であり、Cの型情報をGoの型に変換し、GoとCの間の呼び出し規約を処理します。
-
typeConv
構造体へのgoVoid
フィールドの追加:type typeConv struct { // ... 既存のフィールド ... goVoid ast.Expr // _Ctype_void, denotes C's void }
goVoid
は、Cのvoid
型をGoの抽象構文木(AST)内で表現するためのast.Expr
です。これは_Ctype_void
という識別子に初期化され、cgo
がvoid
型を特別な方法で扱うための内部的なマーカーとして機能します。 -
typeConv.Init
でのgoVoid
の初期化:func (c *typeConv) Init(ptrSize, intSize int64) { // ... 既存の初期化 ... c.goVoid = c.Ident("_Ctype_void") }
_Ctype_void
は、cgo
が生成するGoコード内でCのvoid
型を表すために使用される識別子です。 -
typeConv.Type
でのdwarf.VoidType
の処理:case *dwarf.VoidType: t.Go = c.goVoid t.C.Set("void") t.Align = 1
dwarf.VoidType
(DWARFデバッグ情報におけるvoid
型)が検出された場合、対応するGoの型表現としてc.goVoid
(_Ctype_void
)が設定されます。t.Align = 1
は、void
型がメモリ上で1バイトの境界にアラインされることを示唆しており、cgo
がvoid
関数からの戻り値を処理する際に、errno
を格納するための内部的な構造を適切に配置できるようにします。 -
typeConv.FuncType
でのdwarf.FuncType
の戻り値処理の改善:var r *Type var gr []*ast.Field if _, ok := dtype.ReturnType.(*dwarf.VoidType); ok { gr = []*ast.Field{{Type: c.goVoid}} } else if dtype.ReturnType != nil { r = c.Type(dtype.ReturnType, pos) gr = []*ast.Field{{Type: r.Go}} }
この変更は、C関数の戻り値の型が
dwarf.VoidType
である場合に、Goの戻り値のフィールドリストgr
に_Ctype_void
型を持つフィールドを追加するようにします。これにより、void
関数がGoの世界で_Ctype_void
とerror
の2つの戻り値を持つかのように扱われ、errno
がGoのerror
に正しくマッピングされるようになります。 -
rewriteRef
関数からのエラーチェックの削除:// 変更前: // if r.Context == "call2" { // if r.Name.FuncType.Result == nil { // error_(r.Pos(), "assignment count mismatch: 2 = 0") // } // // ... // } // 変更後: // if r.Context == "call2" { // // Invent new Name for the two-result function. // n := f.Name["2"+r.Name.Go] // // ... // }
rewriteRef
関数は、Goのコード内でC関数呼び出しをGoのセマンティクスに合うように書き換える役割を担っています。以前は、call2
(2つの戻り値を持つGo関数呼び出し)のコンテキストでCのvoid
関数が呼び出された場合、戻り値がないため「assignment count mismatch」エラーが発生していました。しかし、上記の型マッピングの変更により、void
関数もGoの世界で_Ctype_void
とerror
の2つの戻り値を持つように扱われるようになったため、このエラーチェックは不要となり削除されました。
テストファイルの追加
-
misc/cgo/test/issue3729.go
: このファイルは、void
を返すC関数g()
と、複数の引数を持つvoid
関数g2()
を定義しています。これらのC関数は、それぞれerrno
を設定します。Goのテスト関数test3729
は、これらのC関数を呼び出し、返されたerror
が期待されるerrno
値(syscall.E2BIG
やsyscall.EINVAL
)と一致するかを検証します。これにより、cgo
がvoid
関数からのerrno
を正しくGoのerror
に変換していることを確認します。 -
misc/cgo/test/issue3729w.go
: このファイルはWindows環境向けのテストです。Windowsではerrno
の動作がUnix系システムと異なるため、このテストは単にt.Log("skip errno test on Windows")
と出力してテストをスキップします。これは、プラットフォーム間の差異を考慮したテスト戦略の一例です。
これらの変更とテストの追加により、cgo
はvoid
を返すC関数からのerrno
アクセスを正確に処理できるようになり、GoとCの相互運用性が向上しました。
関連リンク
- Go Issue 3729: https://github.com/golang/go/issues/3729
- Gerrit Change List 6938052: https://golang.org/cl/6938052
参考にした情報源リンク
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQG7AfSXg4Lv3MYOPMAtlQP0-OPL_ArKMGoLrHrxFqKQOwani4Ge3O5TO0nle6kOfcNcPk4MkKBmJ5S0mx3zos1SwjcOD-AaHR6ZZYtDfK2N3fNEQVPXc2xHoaytJ9osAepWLRVBqiLyh3v3ZJaoK0YeJHM9QTPKFGznhClKza2e
- https://vertexaisearch.cloud.google.com/grounding-api-redirect/AUZIYQHU841QTPn_ftYaelNd4fWkll3ce0zhiODhMM1R5kpocRLdzQ1ZYqfB424gIoylNzMsSjM7ITijmFPSF7qspnfgLy08Dl6dlRlWbGlriBBWpgNZKavlUExEJgqL07UyVfI_mFY-UqIAfn_Ujco13qymE9DNZq3GtgkGiZ7qUzrK8M2caXJD2ReSx_6GuXuorsARsBIngySlkskO675Q