[インデックス 19502] ファイルの概要
このコミットは、GoのCgoツールにおける、匿名(untagged)構造体のtypedef
がGoのexport
された関数で使用された場合に発生するコンパイルエラーを修正します。具体的には、CgoがCのtypedef
された匿名構造体をGoの型に変換する際の内部的な処理を改善し、Cコード内でtypedef
名が正しく参照されるようにします。
コミット
cmd/cgo: for typedef of untagged struct, use typedef name in C code
Fixes #8148.
LGTM=cookieo9, rsc
R=rsc, cookieo9
CC=golang-codereviews
https://golang.org/cl/103080043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c22ed1290c55e7ca0e8cbd7028ddd4c397a71174
元コミット内容
cmd/cgo: for typedef of untagged struct, use typedef name in C code
変更の背景
このコミットは、GoのCgoツールが抱えていた特定の問題、すなわちGoのexport
された関数がC言語の匿名(untagged)構造体のtypedef
型を引数として受け取る際に発生するコンパイルエラー(Issue 8148)を解決するために導入されました。
C言語では、構造体に名前(タグ)を付けずにtypedef
を使って新しい型名を定義することが可能です。例えば、typedef struct { int i; } T;
のように記述します。この場合、T
はint
型のメンバi
を持つ匿名構造体の別名となります。
Cgoは、GoプログラムとC言語のコードを連携させるためのツールであり、Cの型をGoの型に適切に変換する役割を担っています。しかし、これまでのCgoの実装では、このような匿名構造体のtypedef
をGoのexport
された関数(C言語から呼び出されるGo関数)で使用しようとすると、Cgoが生成する内部的なCコードにおいて、typedef
名ではなく、匿名構造体自体が持つ内部的な名前(例えば、struct __anon0
のようなコンパイラが生成する名前)を参照しようとしていました。
この不一致が原因で、Cgoが生成するCコードと、Goのexport
された関数が期待する型定義との間で齟齬が生じ、結果としてコンパイルエラーが発生していました。このコミットは、この型名の不一致を解消し、Cgoが匿名構造体のtypedef
を正しく処理できるようにすることで、この問題を修正します。
前提知識の解説
このコミットの理解を深めるために、以下の前提知識を解説します。
-
Cgo: Go言語の標準ライブラリの一部であり、GoプログラムからC言語の関数を呼び出したり、C言語のデータ構造を利用したりするためのメカニズムを提供します。Cgoは、GoとCの間のインターフェースコードを自動生成し、型変換やメモリ管理の橋渡しを行います。Goのソースファイル内に
import "C"
と記述し、その直前のコメントブロックにC言語のコードを記述することでCgoを利用できます。 -
匿名(Untagged)構造体: C言語において、構造体は通常、
struct MyStruct { ... };
のようにタグ(MyStruct
)を持ちます。しかし、タグを付けずにtypedef
と組み合わせて使用することも可能です。 例:typedef struct { int x; int y; } Point;
この場合、Point
はint x
とint y
を持つ構造体の新しい型名となりますが、struct
キーワードの後にタグは指定されていません。このような構造体を匿名構造体と呼びます。 -
typedef
: C言語のキーワードで、既存のデータ型に新しい別名(エイリアス)を定義するために使用されます。これにより、コードの可読性を向上させたり、複雑な型宣言を簡略化したりできます。 例:typedef unsigned int UINT;
(unsigned int
にUINT
という別名を定義) 構造体と組み合わせて、typedef struct { ... } MyType;
のように使用されることが非常に多いです。 -
Cgoにおける型変換の課題: CgoはCの型をGoの型に自動的にマッピングしますが、CとGoの型システムには根本的な違いがあります。特に、Cのポインタ、共用体、ビットフィールド、そして匿名構造体などは、Goの型システムに直接対応するものがなく、Cgoが複雑な変換ロジックを必要とします。匿名構造体の場合、CgoがGo側で生成する型名が予測しにくく、Goコードから直接参照するのが困難になることがあります。
-
src/cmd/cgo/gcc.go
の役割:cmd/cgo
ツールの一部であるgcc.go
ファイルは、Cgoのコンパイルプロセスにおいて重要な役割を担っています。このファイルは、Cコンパイラ(GCC)のデバッグ情報などを解析し、C言語の型定義をGoの型に適切に変換するためのロジックを含んでいます。CgoがCの型を理解し、Goのコードから利用できるようにするための「型変換エンジン」のようなものです。
技術的詳細
このコミットが解決する問題の核心は、CgoがCの匿名構造体のtypedef
を処理する際に、そのtypedef
によって与えられた型名(例: T
)ではなく、Cコンパイラが内部的に生成する匿名構造体の名前(例: struct __anon0
)をGo側で参照しようとしていた点にあります。
Goのexport
された関数は、CgoによってC言語から呼び出せるようにラップされます。このラッパーコードを生成する際、CgoはGoの関数の引数型をCの型にマッピングする必要があります。問題のシナリオでは、Goの関数がC.T
のような型(Cのtypedef struct { int i; } T;
に対応)を引数として受け取ると、Cgoは内部的に生成するCコードで、この型をT
としてではなく、匿名構造体の内部名で参照しようとしました。しかし、CのソースコードではT
というtypedef
名が使われているため、型名の不一致が発生し、コンパイルエラーとなっていました。
このコミットは、src/cmd/cgo/gcc.go
内の型変換ロジックを修正することで、この問題を解決します。具体的には、typeConv
構造体のType
メソッドに新しいロジックが追加されました。このメソッドは、Cの型情報をGoの型に変換する主要な部分です。
修正の目的は、匿名構造体のtypedef
が検出された場合に、CgoがCコード内でtypedef
名(例: T
)を正しく使用するようにすることです。これにより、Goのexport
された関数がCのtypedef
された匿名構造体を引数として受け取る際に、Cgoが生成するCコードとGoの型定義が一致し、コンパイルが成功するようになります。
コアとなるコードの変更箇所
このコミットによる主要なコード変更は以下の2つのファイルで行われています。
-
misc/cgo/test/issue8148.go
: このファイルは、Issue 8148で報告された問題を再現し、このコミットによる修正が正しく機能することを検証するための新しいテストケースです。// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Issue 8148. A typedef of an unnamed struct didn't work when used // with an exported Go function. No runtime test; just make sure it // compiles. package cgotest /* typedef struct { int i; } T; int issue8148Callback(T*); static int get() { T t; t.i = 42; return issue8148Callback(&t); } */ import "C" //export issue8148Callback func issue8148Callback(t *C.T) C.int { return t.i } func Issue8148() int { return int(C.get()) }
このテストケースでは、Cのコードブロック内で
typedef struct { int i; } T;
という匿名構造体のtypedef
を定義し、issue8148Callback
というGoのexport
された関数がT*
型を引数として受け取るようにしています。Cのget()
関数からissue8148Callback
を呼び出し、コンパイルが成功することを確認します。 -
src/cmd/cgo/gcc.go
: Cgoの型変換ロジックが修正されたファイルです。--- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -1283,6 +1283,11 @@ func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type { if isStructUnionClass(sub.Go) || *godefs || *cdefs { t.Go = sub.Go + if isStructUnionClass(sub.Go) { + // Use the typedef name for C code. + typedef[sub.Go.(*ast.Ident).Name].C = t.C + } + // If we've seen this typedef before, and it // was an anonymous struct/union/class before // too, use the old definition.
コアとなるコードの解説
src/cmd/cgo/gcc.go
における変更は、typeConv
構造体のType
メソッド内で行われています。このメソッドは、CのDWARF(Debugging With Attributed Record Formats)型情報を受け取り、それをGoの対応する型に変換するプロセスを管理しています。
追加されたコードブロックは以下の通りです。
if isStructUnionClass(sub.Go) {
// Use the typedef name for C code.
typedef[sub.Go.(*ast.Ident).Name].C = t.C
}
-
if isStructUnionClass(sub.Go)
: この条件は、sub.Go
がGoの抽象構文木(AST)における構造体、共用体、またはクラスを表す識別子である場合に真となります。これは、Cの匿名構造体のtypedef
がGo側でどのように表現されているかをチェックしています。 -
typedef[sub.Go.(*ast.Ident).Name].C = t.C
: この行が修正の核心です。sub.Go.(*ast.Ident).Name
: これは、GoのASTノードsub.Go
を*ast.Ident
型に型アサートし、そのName
フィールド(識別子の名前)を取得しています。この名前は、Cのtypedef struct { ... } T;
におけるT
のような、Go側でC.T
として参照される型名に対応します。typedef[...]
:typedef
は、Goの型名とそれに対応するCの型情報のマッピングを保持するマップです。.C = t.C
: ここで、sub.Go.(*ast.Ident).Name
で取得したGoの型名(例:C.T
)に対応するCの型情報を、現在の変換対象の型t
のC表現(t.C
)に設定しています。
この変更により、Cgoは匿名構造体のtypedef
を処理する際に、そのtypedef
名をCコードでの参照名として正しく認識し、Goのexport
された関数がその型を安全に利用できるようになります。以前は、Cgoが内部的な匿名構造体名を使用しようとしていたため、Cのソースコードで定義されたtypedef
名との間に不一致が生じていましたが、この修正によってその不一致が解消されました。
関連リンク
- Go CL (Change List): https://golang.org/cl/103080043
- Go Issue: https://github.com/golang/go/issues/8148 (コミットメッセージに記載されているが、公開リポジトリでは見つからない可能性あり)
参考にした情報源リンク
- Go cgo untagged struct typedef に関するWeb検索結果
- Go cgo gcc.go type conversion に関するWeb検索結果
- Go言語のCgoに関する公式ドキュメントおよび関連するコミュニティの議論