[インデックス 19461] ファイルの概要
このコミットは、GoのCgoツールにおける型変換の挙動を修正し、特にC言語のtypedef struct S Tのような定義において、C.TとC.struct_SがGoの型システム上で互換性を持つように再調整するものです。これは、以前のGo 1.3での変更によって既存のCgoコードが動作しなくなる問題を解決するために導入されました。
コミット
commit 0782ee3ad57a21bd3566f20e76e4e453613e7a23
Author: Russ Cox <rsc@golang.org>
Date: Wed May 28 14:04:31 2014 -0400
cmd/cgo: given typedef struct S T, make C.T and C.struct_S interchangeable
For incomplete struct S, C.T and C.struct_S were interchangeable in Go 1.2
and earlier, because all incomplete types were interchangeable
(even C.struct_S1 and C.struct_S2).
CL 76450043, which fixed issue 7409, made different incomplete types
different from Go's point of view, so that they were no longer completely
interchangeable.
However, imprecision about C.T and C.struct_S - really the same
underlying C type - is the one behavior enabled by the bug that
is most likely to be depended on by existing cgo code.
Explicitly allow it, to keep that code working.
Fixes #7786.
LGTM=iant, r
R=golang-codereviews, iant, r
CC=golang-codereviews
https://golang.org/cl/98580046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0782ee3ad57a21bd3566f20e76e4e453613e7a23
元コミット内容
cmd/cgo: given typedef struct S T, make C.T and C.struct_S interchangeable
For incomplete struct S, C.T and C.struct_S were interchangeable in Go 1.2
and earlier, because all incomplete types were interchangeable
(even C.struct_S1 and C.struct_S2).
CL 76450043, which fixed issue 7409, made different incomplete types
different from Go's point of view, so that they were no longer completely
interchangeable.
However, imprecision about C.T and C.struct_S - really the same
underlying C type - is the one behavior enabled by the bug that
is most likely to be depended on by existing cgo code.
Explicitly allow it, to keep that code working.
Fixes #7786.
変更の背景
このコミットは、Go 1.3におけるCgoの型変換に関する重要な変更の巻き戻し、または調整として位置づけられます。
Go 1.2以前のCgoでは、C言語の不完全型(struct S;のように宣言されたが、そのメンバーがまだ定義されていない構造体)の扱いに関して、ある種の「バグ」が存在していました。具体的には、Cgoは異なる不完全型であっても、Goの型システム上では互いに区別せず、すべてを互換性のあるものとして扱っていました。例えば、struct S1;とstruct S2;という異なる不完全型がGo側では同じ型として扱われるような状況です。
この挙動は、Go 1.3の開発中にCL 76450043(Issue 7409を修正したものとされていますが、このCLとIssueの具体的な内容は、Goの公開リポジトリでは直接確認できませんでした。コミットメッセージの文脈から、Cgoの型システムにおける不完全型の厳密な区別に関する変更であったと推測されます)によって修正されました。この修正により、Goは異なる不完全型をそれぞれ異なるGoの型として認識するようになり、より厳密な型チェックが可能になりました。これは、型安全性の観点からは正しい改善でした。
しかし、この修正には副作用がありました。C言語では、typedef struct S T;のように、既存の構造体(struct S)に新しい型名(T)を付けることがよく行われます。この場合、Tとstruct SはC言語の観点からは全く同じ基底型を指します。Go 1.2以前のCgoの「バグ」は、このtypedefされた型(C.T)と元の構造体型(C.struct_S)がGo側で互換性を持つという、意図せずも便利な挙動を提供していました。Go 1.3での厳密化により、この互換性が失われ、多くの既存のCgoコードがコンパイルエラーとなる事態が発生しました。
この互換性の喪失は、特に既存のCライブラリのAPIをGoから利用する際に問題となりました。Cライブラリがtypedefされた構造体ポインタを引数に取る関数を提供している場合、Go側でC.struct_S型の変数をC.T型の引数に渡すことができなくなり、コードの修正が必要になったのです。
このコミットは、この後方互換性の問題を解決するために導入されました。Issue 7786(このコミットによって修正される問題)は、まさにこのtypedefされた構造体型と元の構造体型との間の互換性が失われたことによって発生したものです。このコミットは、Cgoがtypedef struct S Tのようなケースにおいて、C.TとC.struct_SをGoの型システム上で再び互換性のあるものとして扱うように変更することで、既存のCgoコードが引き続き動作するようにします。これは、不完全型全般の厳密な区別というGo 1.3の変更の原則を維持しつつ、実用的な互換性のニーズに対応するための調整と言えます。
前提知識の解説
このコミットの理解には、以下の概念が不可欠です。
-
Cgo:
- CgoはGo言語のツールであり、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのメカニズムを提供します。
- GoとCの間のデータ型変換、関数呼び出し規約の調整などをCgoが自動的に行います。
- Goコード内で
import "C"と記述し、その直前のコメントブロックにC言語のコードを記述することで、CgoがそのCコードをGoから利用可能なように変換します。 - Cgoは、Cの型をGoの型にマッピングする際に、Goの型システムに適合するように変換を行います。例えば、Cの
structはGoのstructに、CのポインタはGoのポインタに変換されます。
-
C言語の
typedef:typedefはC言語のキーワードで、既存のデータ型に新しい名前(エイリアス)を定義するために使用されます。- 例:
typedef unsigned int UINT;はunsigned int型にUINTという新しい名前を付けます。 - 構造体や共用体に対してもよく使われます。例:
typedef struct MyStruct { int x; } MyStruct; - この場合、
struct MyStructとMyStructはC言語の観点からは全く同じ型を指します。typedefは単に既存の型に別名を与えるだけであり、新しい型を定義するわけではありません。
-
C言語の不完全型(Incomplete Types / Opaque Types):
- 不完全型とは、そのサイズやメンバー構成がまだコンパイラに知らされていない型のことです。
- 最も一般的なのは、前方宣言された構造体や共用体です。例:
struct MyStruct; - この宣言だけでは
MyStructの具体的な定義は提供されていませんが、その型のポインタ(struct MyStruct *)を宣言したり、関数引数として使用したりすることは可能です。これは、ヘッダーファイルでAPIを宣言し、実装の詳細を隠蔽する(不透明な型、opaque type)ためによく用いられます。 - 不完全型は、そのサイズが不明なため、変数を直接宣言することはできません(例:
struct MyStruct my_var;はエラー)。ポインタとしてのみ扱えます。
-
Goの型システムとCgoの型マッピング:
- Goは静的型付け言語であり、厳密な型システムを持っています。異なる型間の代入や比較は、明示的な型変換がない限り許可されません。
- Cgoは、Cの型をGoの型に変換する際に、Goの型システムに適合するようにします。例えば、Cの
struct SはGoではC.struct_Sという型に、typedef struct S Tで定義されたTはGoではC.Tという型にマッピングされます。 - Go 1.3以前のCgoの「バグ」は、異なる不完全型(例:
struct S1;とstruct S2;)がGo側で同じ型として扱われるというものでした。これは、Goの型システムがCの不完全型の概念を十分に厳密にマッピングできていなかったことに起因します。 - Go 1.3での修正は、この不完全型をGo側で厳密に区別するように変更しました。これにより、
C.struct_S1とC.struct_S2は異なる型として扱われるようになりました。
これらの概念を理解することで、このコミットが解決しようとしている問題の根源と、その解決策の技術的な詳細がより明確になります。
技術的詳細
このコミットの技術的な核心は、CgoがC言語の型情報をGoの型に変換する際のロジック、特にtypedefと不完全型(opaque struct)の扱いを調整することにあります。
Go 1.3での変更(CL 76450043/Issue 7409)以前は、Cgoは不完全な構造体型をGo側で区別せず、すべて同じ型として扱っていました。これは、C言語のstruct S;とtypedef struct S T;のような定義において、C.struct_SとC.TがGo側で互換性を持つという副作用を生んでいました。C言語の観点からはこれらは同じ基底型を指すため、この互換性は多くの既存Cgoコードにとって自然で便利なものでした。
Go 1.3での修正は、この「バグ」を修正し、異なる不完全型をGo側で厳密に区別するようにしました。これにより、C.struct_S1とC.struct_S2のような異なる不完全型はGo側で異なる型として扱われるようになり、型安全性が向上しました。しかし、この修正は、C.TとC.struct_Sの間の互換性も失わせる結果となりました。これは、C言語のtypedefのセマンティクス(単なる別名)をGoの型システムが正確に反映できていないことに起因します。
このコミットは、この特定のケース(typedef struct S T)における互換性を回復させることを目的としています。Cgoの内部では、C言語の型情報はDwarfデバッグ情報から取得され、src/cmd/cgo/gcc.go内のtypeConv構造体がその変換ロジックを担っています。
変更のポイントは以下の通りです。
-
dwarf.StructTypeの処理の調整:typeConv.Typeメソッドは、Dwarfの型情報(dtype)をGoの型に変換します。dwarf.StructTypeを処理する際、以前はdt.ByteSize < 0(不完全型)の場合にbreakしていました。これは、不完全型に対する特別な処理を意味していました。- 今回の変更では、不完全型であっても、タグ(
StructName)がある場合は処理を続行するようにしました。これにより、typedef struct S Tのようなケースで、struct Sが不完全型であっても、そのタグ情報が利用されるようになります。 - 特に、不完全な構造体型に対して、Goの表現を決定しようとせずに、
_Ctype_struct_Sのような名前を生成し、その型をstruct{}として扱うように調整されました。これは、Go側でその型の具体的なレイアウトを知る必要がない(ポインタとしてのみ扱うため)場合に、Goの型システムに適合させるためのものです。
-
typedef型のGo表現の調整:dwarf.TypedefTypeを処理する際、typedefされた型(t)のGo表現(t.Go)を、元の型(sub.Go)のGo表現と同じにするロジックが追加されました。- 具体的には、
sub.Go.Nameが_Ctype_struct_foo、_Ctype_union_foo、_Ctype_class_fooのような形式である場合(つまり、元の型がCの構造体、共用体、クラスである場合)、typedefされた型も元の型と同じGo表現を持つようにします。 - これにより、
typedef struct S T;の場合、C.TとC.struct_SがGo側で同じ基底型(_Ctype_struct_S)を指すようになり、互換性が回復します。 isStructUnionClassという新しいヘルパー関数が追加され、Goの識別子がCの構造体、共用体、クラスのタグ付き型を表すかどうかを判定します。
-
サイズ不明な型の処理の調整:
t.Size < 0(サイズ不明な型)の場合の処理も調整されました。- 以前は、
typedef型でない限り[0]byteとして扱われていましたが、今回の変更では、typedef型であるか、タグ付きの構造体型である場合は、そのGo表現を維持するようにしました。これにより、不完全型であっても、typedefによって与えられた名前や元の構造体名がGo側で適切に扱われるようになります。
これらの変更により、Cgoはtypedef struct S TのようなCの定義をGoの型に変換する際に、C.TとC.struct_SがGoの型システム上で互換性を持つように調整されます。これは、C言語のtypedefのセマンティクスをより正確にGoの型システムに反映させるための、Cgoの型変換ロジックの洗練と言えます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/cmd/cgo/gcc.goファイルに集中しています。また、この変更の意図と効果を示すために、新しいテストケースmisc/cgo/test/issue7786.goが追加されています。doc/go1.3.htmlは、この変更がGo 1.3のリリースノートにどのように反映されるかを示すドキュメントの更新です。
src/cmd/cgo/gcc.go
このファイルはCgoのバックエンドの一部であり、C言語の型情報をGoの型に変換するロジックを含んでいます。
func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Typeメソッド内の変更:case *dwarf.StructType:ブロック:- 不完全な構造体(
dt.ByteSize < 0)の処理が変更されました。以前はすぐにbreakしていましたが、タグ(dt.StructName)がある場合は処理を続行するように変更されました。 - 不完全な構造体に対して、Goの表現を決定しようとせずに、
_Ctype_struct_プレフィックスを持つ名前を生成し、その型をstruct{}として扱うロジックが追加されました。これは、Go側でその型の具体的なレイアウトを知る必要がない(ポインタとしてのみ扱うため)場合に、Goの型システムに適合させるためのものです。
// 変更前: // if dt.ByteSize < 0 { // opaque struct // break // } // 変更後: if dt.ByteSize < 0 && tag == "" { // opaque unnamed struct - should not be possible break } // ... if dt.ByteSize < 0 { // Size calculation in c.Struct/c.Opaque will die with size=-1 (unknown), // so execute the basic things that the struct case would do // other than try to determine a Go representation. tt := *t tt.C = &TypeRepr{"%s %s", []interface{}{dt.Kind, tag}} tt.Go = c.Ident("struct{}") typedef[name.Name] = &tt break }- 不完全な構造体(
case *dwarf.TypedefType:ブロック:typedefされた型のGo表現を、元の型(sub.Go)のGo表現と同じにする条件が拡張されました。- 以前は
-godefsまたは-cdefsモードの場合にのみ行われていましたが、元の型がCの構造体、共用体、またはクラスである場合にも適用されるようになりました。
// 変更前: // if *godefs || *cdefs { // t.Go = sub.Go // } // 変更後: // If sub.Go.Name is "_Ctype_struct_foo" or "_Ctype_union_foo" or "_Ctype_class_foo", // use that as the Go form for this typedef too, so that the typedef will be interchangeable // with the base type. // In -godefs and -cdefs mode, do this for all typedefs. if isStructUnionClass(sub.Go) || *godefs || *cdefs { t.Go = sub.Go }- サイズ不明な型(
t.Size < 0)の処理:- サイズ不明な型を
[0]byteとして扱う条件が変更されました。typedef型またはタグ付きの構造体型の場合は、そのGo表現を維持するようにしました。
// 変更前: // if _, ok := dtype.(*dwarf.TypedefType); !ok { // t.Go = c.Opaque(0) // } // 変更後: switch dt := dtype.(type) { case *dwarf.TypedefType: // ok case *dwarf.StructType: if dt.StructName != "" { break } t.Go = c.Opaque(0) default: t.Go = c.Opaque(0) } - サイズ不明な型を
func isStructUnionClass(x ast.Expr) bool関数の追加:- Goの抽象構文木(AST)の式が、Cの構造体、共用体、またはクラスのタグ付き型を表すかどうかを判定するヘルパー関数が追加されました。これは、
_Ctype_struct_、_Ctype_union_、_Ctype_class_のプレフィックスを持つ識別子をチェックします。
- Goの抽象構文木(AST)の式が、Cの構造体、共用体、またはクラスのタグ付き型を表すかどうかを判定するヘルパー関数が追加されました。これは、
misc/cgo/test/issue7786.go
このファイルは、このコミットによって解決される問題(Issue 7786)を再現し、修正が正しく機能することを確認するための新しいテストケースです。
- C言語のコードブロックで、不完全な構造体
test7786と、それに対するtypedefであるtypedef_test7786が宣言されています。 - 同様に、完全な構造体
body7786とそのtypedefであるtypedef_body7786、そして共用体union7786とそのtypedefであるtypedef_union7786も宣言されています。 - Goの
f()関数内で、*C.typedef_test7786と*C.struct_test7786の間の代入が試みられています(x1 = x2、x2 = x1)。 - 同様に、
typedefされた完全な構造体と元の構造体、およびtypedefされた共用体と元の共用体の間の代入もテストされています。 - これらの代入がコンパイル時にエラーとならないことが、このテストの目的です。これは、
C.TとC.struct_SがGoの型システム上で互換性を持つようになったことを確認します。
doc/go1.3.html
Go 1.3のリリースノートに、この変更に関する説明が追加されました。
- Go 1.3が異なる不完全型を異なるGoの型として扱うようになったこと、そしてそれが以前のバグを修正したものであることが述べられています。
- しかし、
typedef struct S TのようなケースでC.struct_SとC.Tが互換性を持つという、既存のCgoコードが依存していた挙動が、このコミットによって明示的に許可されたことが説明されています。 - また、
*C.FILEのようなC型をパッケージAPIとして公開することは推奨されないという注意喚起も含まれています。
これらの変更箇所は、Cgoの型変換ロジックの内部的な調整と、その調整が既存のCgoコードの後方互換性に与える影響を考慮したものであることを示しています。
コアとなるコードの解説
ここでは、src/cmd/cgo/gcc.goにおける主要な変更点を詳細に解説します。
func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type メソッド
このメソッドは、Dwarfデバッグ情報から取得したCの型情報(dtype)を、Goの型システムで表現可能な*Typeオブジェクトに変換するCgoの核心部分です。
case *dwarf.StructType: ブロックの変更
// 変更前:
// if dt.ByteSize < 0 { // opaque struct
// break
// }
// 変更後:
if dt.ByteSize < 0 && tag == "" { // opaque unnamed struct - should not be possible
break
}
// ... (中略) ...
if dt.ByteSize < 0 {
// Size calculation in c.Struct/c.Opaque will die with size=-1 (unknown),
// so execute the basic things that the struct case would do
// other than try to determine a Go representation.
tt := *t
tt.C = &TypeRepr{"%s %s", []interface{}{dt.Kind, tag}}
tt.Go = c.Ident("struct{}")
typedef[name.Name] = &tt
break
}
dt.ByteSize < 0: これは、Dwarf情報においてその構造体のサイズが不明であることを示し、C言語の不完全型(opaque struct)に対応します。- 変更前: 不完全型の場合、この
caseブロックの処理をすぐにbreakしていました。これは、不完全型に対する特別な(あるいは不十分な)扱いを意味していました。 - 変更後:
if dt.ByteSize < 0 && tag == ""という条件が追加されました。これは「不完全でかつ名前(タグ)がない構造体」の場合にのみbreakすることを意味します。このようなケースは通常ありえないとコメントされています。- 重要なのは、
dt.ByteSize < 0(不完全型)であっても、tag(構造体名、例:struct SのS)が存在する場合は、その後の処理が続行されるようになった点です。 - さらに、不完全型の場合に新しいロジックが追加されました。
tt := *tで現在の型tのコピーを作成し、そのGo表現をc.Ident("struct{}")、つまりGoの空の構造体struct{}に設定しています。これは、Go側でその型の具体的なメモリレイアウトを知る必要がない(ポインタとしてのみ扱うため)場合に、Goの型システムに適合させるためのものです。typedef[name.Name] = &ttで、この新しい型定義をtypedefマップに登録します。 - この変更により、
struct S;のような不完全型であっても、_Ctype_struct_SのようなGoの型名が生成され、その型がstruct{}として扱われるようになります。
case *dwarf.TypedefType: ブロックの変更
// 変更前:
// if *godefs || *cdefs {
// t.Go = sub.Go
// }
// 変更後:
// If sub.Go.Name is "_Ctype_struct_foo" or "_Ctype_union_foo" or "_Ctype_class_foo",
// use that as the Go form for this typedef too, so that the typedef will be interchangeable
// with the base type.
// In -godefs and -cdefs mode, do this for all typedefs.
if isStructUnionClass(sub.Go) || *godefs || *cdefs {
t.Go = sub.Go
}
dwarf.TypedefType: これはC言語のtypedef定義に対応します。subはtypedefの元の型です。t.Go = sub.Go: これは、typedefされた型(t)のGo表現を、元の型(sub)のGo表現と全く同じにするという意味です。これにより、Goの型システム上で両者が互換性を持つようになります。- 変更前: この互換性を持たせる処理は、CgoがGoの定義ファイルを生成するモード(
-godefs)またはCの定義ファイルを生成するモード(-cdefs)の場合にのみ行われていました。 - 変更後:
isStructUnionClass(sub.Go)という新しい条件が追加されました。これは、元の型subのGo表現が、Cの構造体、共用体、またはクラスのタグ付き型(例:_Ctype_struct_S)である場合に、常にt.Go = sub.Goを実行することを意味します。 - この変更が、
typedef struct S T;の場合にC.TとC.struct_Sが互換性を持つようにする核心的な部分です。C.TのGo表現がC.struct_SのGo表現と同じになるため、Goの型システムはこれらを同じ型として扱います。
サイズ不明な型(t.Size < 0)の処理の変更
// 変更前:
// if _, ok := dtype.(*dwarf.TypedefType); !ok {
// t.Go = c.Opaque(0)
// }
// 変更後:
switch dt := dtype.(type) {
case *dwarf.TypedefType:
// ok
case *dwarf.StructType:
if dt.StructName != "" {
break
}
t.Go = c.Opaque(0)
default:
t.Go = c.Opaque(0)
}
t.Size < 0: これは、Goの型に変換された後の型が、そのサイズが不明であることを示します。これは通常、不完全型や、Go側で具体的なサイズを持つ必要がない型(ポインタとしてのみ扱われる型など)に適用されます。c.Opaque(0): これはGoの[0]byte型を生成します。サイズが不明な型をGoで表現する際の一般的な方法です。- 変更前:
typedef型でない限り、サイズ不明な型はすべて[0]byteとして扱われていました。 - 変更後:
switch文が導入され、より詳細な条件分岐が行われます。*dwarf.TypedefTypeの場合: 何もせず、既存のGo表現を維持します。これは、typedefされた不完全型が、元の不完全型と同じGo表現を持つようにする上記の変更と連携します。*dwarf.StructTypeの場合: もし構造体に名前(dt.StructName != "")があれば、breakして既存のGo表現を維持します。これは、タグ付きの不完全構造体(例:struct S;)が[0]byteにならないようにします。名前がない構造体(匿名構造体)の場合はc.Opaque(0)となります。- それ以外の場合:
c.Opaque(0)となります。
- この変更により、
typedefされた不完全型や、タグ付きの不完全構造体が、Go側でより適切にその名前を保持し、[0]byteに一律に変換されることを防ぎます。
func isStructUnionClass(x ast.Expr) bool 関数の追加
func isStructUnionClass(x ast.Expr) bool {
id, ok := x.(*ast.Ident)
if !ok {
return false
}
name := id.Name
return strings.HasPrefix(name, "_Ctype_struct_") ||
strings.HasPrefix(name, "_Ctype_union_") ||
strings.HasPrefix(name, "_Ctype_class_")
}
- このヘルパー関数は、GoのASTノード
xが識別子(ast.Ident)であり、その名前がCgoが生成するCの構造体、共用体、またはクラスの型名(_Ctype_struct_、_Ctype_union_、_Ctype_class_で始まるもの)であるかどうかをチェックします。 - この関数は、上記の
case *dwarf.TypedefType:ブロックで利用され、typedefされた型が元のCの構造体、共用体、クラス型と互換性を持つべきかどうかを判断するために使用されます。
これらの変更は、CgoがC言語の複雑な型システム(特にtypedefと不完全型)をGoの型システムにマッピングする際の精度と柔軟性を向上させるものです。これにより、Go 1.3で導入された厳密な型チェックの原則を維持しつつ、既存のCgoコードの後方互換性を確保するという、実用的なバランスが取られています。
関連リンク
- Go GitHub Commit: https://github.com/golang/go/commit/0782ee3ad57a21bd3566f20e76e4e453613e7a23
- Go Code Review: https://golang.org/cl/98580046
- Go Issue 7786: コミットメッセージに
Fixes #7786と記載されています。これは、Go 1.3での型厳密化によって発生した互換性問題を追跡するためのIssueであると推測されます。 - Go Issue 7409: コミットメッセージに
CL 76450043, which fixed issue 7409と記載されています。これは、Go 1.3で不完全型の厳密な区別を導入した元の変更に関連するIssueであると推測されます。 - Go 1.3 Release Notes (doc/go1.3.html): このコミットによって更新されたドキュメントは、Go 1.3のリリースノートの一部として、Cgoの型変換に関するこの変更の背景と影響を説明しています。
参考にした情報源リンク
- Go言語の公式ソースコード(特に
src/cmd/cgo/gcc.go、misc/cgo/test/issue7786.go、doc/go1.3.html) - コミットメッセージ自体
- C言語の
typedefと不完全型に関する一般的な知識 - Go言語のCgoに関する一般的な知識
- Dwarfデバッグ情報に関する一般的な知識
(注: コミットメッセージに記載されているCL 76450043およびIssue 7409については、Goの公開リポジトリや一般的な検索では直接関連する情報を見つけることができませんでした。これらは内部的なトラッキング番号であるか、非常に古い情報である可能性があります。したがって、本解説ではコミットメッセージの文脈からその内容を推測し、Goのソースコードの変更点から技術的詳細を導き出しています。)# [インデックス 19461] ファイルの概要
このコミットは、GoのCgoツールにおける型変換の挙動を修正し、特にC言語のtypedef struct S Tのような定義において、C.TとC.struct_SがGoの型システム上で互換性を持つように再調整するものです。これは、以前のGo 1.3での変更によって既存のCgoコードが動作しなくなる問題を解決するために導入されました。
コミット
commit 0782ee3ad57a21bd3566f20e76e4e453613e7a23
Author: Russ Cox <rsc@golang.org>
Date: Wed May 28 14:04:31 2014 -0400
cmd/cgo: given typedef struct S T, make C.T and C.struct_S interchangeable
For incomplete struct S, C.T and C.struct_S were interchangeable in Go 1.2
and earlier, because all incomplete types were interchangeable
(even C.struct_S1 and C.struct_S2).
CL 76450043, which fixed issue 7409, made different incomplete types
different from Go's point of view, so that they were no longer completely
interchangeable.
However, imprecision about C.T and C.struct_S - really the same
underlying C type - is the one behavior enabled by the bug that
is most likely to be depended on by existing cgo code.
Explicitly allow it, to keep that code working.
Fixes #7786.
LGTM=iant, r
R=golang-codereviews, iant, r
CC=golang-codereviews
https://golang.org/cl/98580046
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0782ee3ad57a21bd3566f20e76e4e453613e7a23
元コミット内容
cmd/cgo: given typedef struct S T, make C.T and C.struct_S interchangeable
For incomplete struct S, C.T and C.struct_S were interchangeable in Go 1.2
and earlier, because all incomplete types were interchangeable
(even C.struct_S1 and C.struct_S2).
CL 76450043, which fixed issue 7409, made different incomplete types
different from Go's point of view, so that they were no longer completely
interchangeable.
However, imprecision about C.T and C.struct_S - really the same
underlying C type - is the one behavior enabled by the bug that
is most likely to be depended on by existing cgo code.
Explicitly allow it, to keep that code working.
Fixes #7786.
変更の背景
このコミットは、Go 1.3におけるCgoの型変換に関する重要な変更の巻き戻し、または調整として位置づけられます。
Go 1.2以前のCgoでは、C言語の不完全型(struct S;のように宣言されたが、そのメンバーがまだ定義されていない構造体)の扱いに関して、ある種の「バグ」が存在していました。具体的には、Cgoは異なる不完全型であっても、Goの型システム上では互いに区別せず、すべてを互換性のあるものとして扱っていました。例えば、struct S1;とstruct S2;という異なる不完全型がGo側では同じ型として扱われるような状況です。
この挙動は、Go 1.3の開発中にCL 76450043(Issue 7409を修正したものとされていますが、このCLとIssueの具体的な内容は、Goの公開リポジトリでは直接確認できませんでした。コミットメッセージの文脈から、Cgoの型システムにおける不完全型の厳密な区別に関する変更であったと推測されます)によって修正されました。この修正により、Goは異なる不完全型をそれぞれ異なるGoの型として認識するようになり、より厳密な型チェックが可能になりました。これは、型安全性の観点からは正しい改善でした。
しかし、この修正には副作用がありました。C言語では、typedef struct S T;のように、既存の構造体(struct S)に新しい型名(T)を付けることがよく行われます。この場合、Tとstruct SはC言語の観点からは全く同じ基底型を指します。Go 1.2以前のCgoの「バグ」は、このtypedefされた型(C.T)と元の構造体型(C.struct_S)がGo側で互換性を持つという、意図せずも便利な挙動を提供していました。Go 1.3での厳密化により、この互換性が失われ、多くの既存のCgoコードがコンパイルエラーとなる事態が発生しました。
この互換性の喪失は、特に既存のCライブラリのAPIをGoから利用する際に問題となりました。Cライブラリがtypedefされた構造体ポインタを引数に取る関数を提供している場合、Go側でC.struct_S型の変数をC.T型の引数に渡すことができなくなり、コードの修正が必要になったのです。
このコミットは、この後方互換性の問題を解決するために導入されました。Issue 7786(このコミットによって修正される問題)は、まさにこのtypedefされた構造体型と元の構造体型との間の互換性が失われたことによって発生したものです。このコミットは、Cgoがtypedef struct S Tのようなケースにおいて、C.TとC.struct_SをGoの型システム上で再び互換性のあるものとして扱うように変更することで、既存のCgoコードが引き続き動作するようにします。これは、不完全型全般の厳密な区別というGo 1.3の変更の原則を維持しつつ、実用的な互換性のニーズに対応するための調整と言えます。
前提知識の解説
このコミットの理解には、以下の概念が不可欠です。
-
Cgo:
- CgoはGo言語のツールであり、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのメカニズムを提供します。
- GoとCの間のデータ型変換、関数呼び出し規約の調整などをCgoが自動的に行います。
- Goコード内で
import "C"と記述し、その直前のコメントブロックにC言語のコードを記述することで、CgoがそのCコードをGoから利用可能なように変換します。 - Cgoは、Cの型をGoの型にマッピングする際に、Goの型システムに適合するように変換を行います。例えば、Cの
structはGoのstructに、CのポインタはGoのポインタに変換されます。
-
C言語の
typedef:typedefはC言語のキーワードで、既存のデータ型に新しい名前(エイリアス)を定義するために使用されます。- 例:
typedef unsigned int UINT;はunsigned int型にUINTという新しい名前を付けます。 - 構造体や共用体に対してもよく使われます。例:
typedef struct MyStruct { int x; } MyStruct; - この場合、
struct MyStructとMyStructはC言語の観点からは全く同じ型を指します。typedefは単に既存の型に別名を与えるだけであり、新しい型を定義するわけではありません。
-
C言語の不完全型(Incomplete Types / Opaque Types):
- 不完全型とは、そのサイズやメンバー構成がまだコンパイラに知らされていない型のことです。
- 最も一般的なのは、前方宣言された構造体や共用体です。例:
struct MyStruct; - この宣言だけでは
MyStructの具体的な定義は提供されていませんが、その型のポインタ(struct MyStruct *)を宣言したり、関数引数として使用したりすることは可能です。これは、ヘッダーファイルでAPIを宣言し、実装の詳細を隠蔽する(不透明な型、opaque type)ためによく用いられます。 - 不完全型は、そのサイズが不明なため、変数を直接宣言することはできません(例:
struct MyStruct my_var;はエラー)。ポインタとしてのみ扱えます。
-
Goの型システムとCgoの型マッピング:
- Goは静的型付け言語であり、厳密な型システムを持っています。異なる型間の代入や比較は、明示的な型変換がない限り許可されません。
- Cgoは、Cの型をGoの型に変換する際に、Goの型システムに適合するようにします。例えば、Cの
struct SはGoではC.struct_Sという型に、typedef struct S Tで定義されたTはGoではC.Tという型にマッピングされます。 - Go 1.3以前のCgoの「バグ」は、異なる不完全型(例:
struct S1;とstruct S2;)がGo側で同じ型として扱われるというものでした。これは、Goの型システムがCの不完全型の概念を十分に厳密にマッピングできていなかったことに起因します。 - Go 1.3での修正は、この不完全型をGo側で厳密に区別するように変更しました。これにより、
C.struct_S1とC.struct_S2は異なる型として扱われるようになりました。
これらの概念を理解することで、このコミットが解決しようとしている問題の根源と、その解決策の技術的な詳細がより明確になります。
技術的詳細
このコミットの技術的な核心は、CgoがC言語の型情報をGoの型に変換する際のロジック、特にtypedefと不完全型(opaque struct)の扱いを調整することにあります。
Go 1.3での変更(CL 76450043/Issue 7409)以前は、Cgoは不完全な構造体型をGo側で区別せず、すべて同じ型として扱っていました。これは、C言語のstruct S;とtypedef struct S T;のような定義において、C.struct_SとC.TがGo側で互換性を持つという副作用を生んでいました。C言語の観点からはこれらは同じ基底型を指すため、この互換性は多くの既存Cgoコードにとって自然で便利なものでした。
Go 1.3での修正は、この「バグ」を修正し、異なる不完全型をGo側で厳密に区別するようにしました。これにより、C.struct_S1とC.struct_S2のような異なる不完全型はGo側で異なる型として扱われるようになり、型安全性が向上しました。しかし、この修正は、C.TとC.struct_Sの間の互換性も失わせる結果となりました。これは、C言語のtypedefのセマンティクス(単なる別名)をGoの型システムが正確に反映できていないことに起因します。
このコミットは、この特定のケース(typedef struct S T)における互換性を回復させることを目的としています。Cgoの内部では、C言語の型情報はDwarfデバッグ情報から取得され、src/cmd/cgo/gcc.go内のtypeConv構造体がその変換ロジックを担っています。
変更のポイントは以下の通りです。
-
dwarf.StructTypeの処理の調整:typeConv.Typeメソッドは、Dwarfの型情報(dtype)をGoの型に変換します。dwarf.StructTypeを処理する際、以前はdt.ByteSize < 0(不完全型)の場合にbreakしていました。これは、不完全型に対する特別な処理を意味していました。- 今回の変更では、不完全型であっても、タグ(
StructName)がある場合は処理を続行するようにしました。これにより、typedef struct S Tのようなケースで、struct Sが不完全型であっても、そのタグ情報が利用されるようになります。 - 特に、不完全な構造体型に対して、Goの表現を決定しようとせずに、
_Ctype_struct_Sのような名前を生成し、その型をstruct{}として扱うように調整されました。これは、Go側でその型の具体的なレイアウトを知る必要がない(ポインタとしてのみ扱うため)場合に、Goの型システムに適合させるためのものです。
-
typedef型のGo表現の調整:dwarf.TypedefTypeを処理する際、typedefされた型(t)のGo表現(t.Go)を、元の型(sub.Go)のGo表現と同じにするロジックが追加されました。- 具体的には、
sub.Go.Nameが_Ctype_struct_foo、_Ctype_union_foo、_Ctype_class_fooのような形式である場合(つまり、元の型がCの構造体、共用体、クラスである場合)、typedefされた型も元の型と同じGo表現を持つようにします。 - これにより、
typedef struct S T;の場合、C.TとC.struct_SがGo側で同じ基底型(_Ctype_struct_S)を指すようになり、互換性が回復します。 isStructUnionClassという新しいヘルパー関数が追加され、Goの識別子がCの構造体、共用体、クラスのタグ付き型を表すかどうかを判定します。
-
サイズ不明な型の処理の調整:
t.Size < 0(サイズ不明な型)の場合の処理も調整されました。- 以前は、
typedef型でない限り[0]byteとして扱われていましたが、今回の変更では、typedef型であるか、タグ付きの構造体型である場合は、そのGo表現を維持するようにしました。これにより、不完全型であっても、typedefによって与えられた名前や元の構造体名がGo側で適切に扱われるようになります。
これらの変更により、Cgoはtypedef struct S TのようなCの定義をGoの型に変換する際に、C.TとC.struct_SがGoの型システム上で互換性を持つように調整されます。これは、C言語のtypedefのセマンティクスをより正確にGoの型システムに反映させるための、Cgoの型変換ロジックの洗練と言えます。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更は、src/cmd/cgo/gcc.goファイルに集中しています。また、この変更の意図と効果を示すために、新しいテストケースmisc/cgo/test/issue7786.goが追加されています。doc/go1.3.htmlは、この変更がGo 1.3のリリースノートにどのように反映されるかを示すドキュメントの更新です。
src/cmd/cgo/gcc.go
このファイルはCgoのバックエンドの一部であり、C言語の型情報をGoの型に変換するロジックを含んでいます。
func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Typeメソッド内の変更:case *dwarf.StructType:ブロック:- 不完全な構造体(
dt.ByteSize < 0)の処理が変更されました。以前はすぐにbreakしていましたが、タグ(dt.StructName)がある場合は処理を続行するように変更されました。 - 不完全な構造体に対して、Goの表現を決定しようとせずに、
_Ctype_struct_プレフィックスを持つ名前を生成し、その型をstruct{}として扱うロジックが追加されました。これは、Go側でその型の具体的なレイアウトを知る必要がない(ポインタとしてのみ扱うため)場合に、Goの型システムに適合させるためのものです。
// 変更前: // if dt.ByteSize < 0 { // opaque struct // break // } // 変更後: if dt.ByteSize < 0 && tag == "" { // opaque unnamed struct - should not be possible break } // ... if dt.ByteSize < 0 { // Size calculation in c.Struct/c.Opaque will die with size=-1 (unknown), // so execute the basic things that the struct case would do // other than try to determine a Go representation. tt := *t tt.C = &TypeRepr{"%s %s", []interface{}{dt.Kind, tag}} tt.Go = c.Ident("struct{}") typedef[name.Name] = &tt break }- 不完全な構造体(
case *dwarf.TypedefType:ブロック:typedefされた型のGo表現を、元の型(sub.Go)のGo表現と同じにする条件が拡張されました。- 以前は
-godefsまたは-cdefsモードの場合にのみ行われていましたが、元の型がCの構造体、共用体、またはクラスである場合にも適用されるようになりました。
// 変更前: // if *godefs || *cdefs { // t.Go = sub.Go // } // 変更後: // If sub.Go.Name is "_Ctype_struct_foo" or "_Ctype_union_foo" or "_Ctype_class_foo", // use that as the Go form for this typedef too, so that the typedef will be interchangeable // with the base type. // In -godefs and -cdefs mode, do this for all typedefs. if isStructUnionClass(sub.Go) || *godefs || *cdefs { t.Go = sub.Go }- サイズ不明な型(
t.Size < 0)の処理:- サイズ不明な型を
[0]byteとして扱う条件が変更されました。typedef型またはタグ付きの構造体型の場合は、そのGo表現を維持するようにしました。
// 変更前: // if _, ok := dtype.(*dwarf.TypedefType); !ok { // t.Go = c.Opaque(0) // } // 変更後: switch dt := dtype.(type) { case *dwarf.TypedefType: // ok case *dwarf.StructType: if dt.StructName != "" { break } t.Go = c.Opaque(0) default: t.Go = c.Opaque(0) } - サイズ不明な型を
func isStructUnionClass(x ast.Expr) bool関数の追加:- Goの抽象構文木(AST)の式が、Cの構造体、共用体、またはクラスのタグ付き型を表すかどうかを判定するヘルパー関数が追加されました。これは、
_Ctype_struct_、_Ctype_union_、_Ctype_class_のプレフィックスを持つ識別子をチェックします。
- Goの抽象構文木(AST)の式が、Cの構造体、共用体、またはクラスのタグ付き型を表すかどうかを判定するヘルパー関数が追加されました。これは、
misc/cgo/test/issue7786.go
このファイルは、このコミットによって解決される問題(Issue 7786)を再現し、修正が正しく機能することを確認するための新しいテストケースです。
- C言語のコードブロックで、不完全な構造体
test7786と、それに対するtypedefであるtypedef_test7786が宣言されています。 - 同様に、完全な構造体
body7786とそのtypedefであるtypedef_body7786、そして共用体union7786とそのtypedefであるtypedef_union7786も宣言されています。 - Goの
f()関数内で、*C.typedef_test7786と*C.struct_test7786の間の代入が試みられています(x1 = x2、x2 = x1)。 - 同様に、
typedefされた完全な構造体と元の構造体、およびtypedefされた共用体と元の共用体の間の代入もテストされています。 - これらの代入がコンパイル時にエラーとならないことが、このテストの目的です。これは、
C.TとC.struct_SがGoの型システム上で互換性を持つようになったことを確認します。
doc/go1.3.html
Go 1.3のリリースノートに、この変更に関する説明が追加されました。
- Go 1.3が異なる不完全型を異なるGoの型として扱うようになったこと、そしてそれが以前のバグを修正したものであることが述べられています。
- しかし、
typedef struct S TのようなケースでC.struct_SとC.Tが互換性を持つという、既存のCgoコードが依存していた挙動が、このコミットによって明示的に許可されたことが説明されています。 - また、
*C.FILEのようなC型をパッケージAPIとして公開することは推奨されないという注意喚起も含まれています。
これらの変更箇所は、Cgoの型変換ロジックの内部的な調整と、その調整が既存のCgoコードの後方互換性に与える影響を考慮したものであることを示しています。
コアとなるコードの解説
ここでは、src/cmd/cgo/gcc.goにおける主要な変更点を詳細に解説します。
func (c *typeConv) Type(dtype dwarf.Type, pos token.Pos) *Type メソッド
このメソッドは、Dwarfデバッグ情報から取得したCの型情報(dtype)を、Goの型システムで表現可能な*Typeオブジェクトに変換するCgoの核心部分です。
case *dwarf.StructType: ブロックの変更
// 変更前:
// if dt.ByteSize < 0 { // opaque struct
// break
// }
// 変更後:
if dt.ByteSize < 0 && tag == "" { // opaque unnamed struct - should not be possible
break
}
// ... (中略) ...
if dt.ByteSize < 0 {
// Size calculation in c.Struct/c.Opaque will die with size=-1 (unknown),
// so execute the basic things that the struct case would do
// other than try to determine a Go representation.
tt := *t
tt.C = &TypeRepr{"%s %s", []interface{}{dt.Kind, tag}}
tt.Go = c.Ident("struct{}")
typedef[name.Name] = &tt
break
}
dt.ByteSize < 0: これは、Dwarf情報においてその構造体のサイズが不明であることを示し、C言語の不完全型(opaque struct)に対応します。- 変更前: 不完全型の場合、この
caseブロックの処理をすぐにbreakしていました。これは、不完全型に対する特別な(あるいは不十分な)扱いを意味していました。 - 変更後:
if dt.ByteSize < 0 && tag == ""という条件が追加されました。これは「不完全でかつ名前(タグ)がない構造体」の場合にのみbreakすることを意味します。このようなケースは通常ありえないとコメントされています。- 重要なのは、
dt.ByteSize < 0(不完全型)であっても、tag(構造体名、例:struct SのS)が存在する場合は、その後の処理が続行されるようになった点です。 - さらに、不完全型の場合に新しいロジックが追加されました。
tt := *tで現在の型tのコピーを作成し、そのGo表現をc.Ident("struct{}")、つまりGoの空の構造体struct{}に設定しています。これは、Go側でその型の具体的なメモリレイアウトを知る必要がない(ポインタとしてのみ扱うため)場合に、Goの型システムに適合させるためのものです。typedef[name.Name] = &ttで、この新しい型定義をtypedefマップに登録します。 - この変更により、
struct S;のような不完全型であっても、_Ctype_struct_SのようなGoの型名が生成され、その型がstruct{}として扱われるようになります。
case *dwarf.TypedefType: ブロックの変更
// 変更前:
// if *godefs || *cdefs {
// t.Go = sub.Go
// }
// 変更後:
// If sub.Go.Name is "_Ctype_struct_foo" or "_Ctype_union_foo" or "_Ctype_class_foo",
// use that as the Go form for this typedef too, so that the typedef will be interchangeable
// with the base type.
// In -godefs and -cdefs mode, do this for all typedefs.
if isStructUnionClass(sub.Go) || *godefs || *cdefs {
t.Go = sub.Go
}
dwarf.TypedefType: これはC言語のtypedef定義に対応します。subはtypedefの元の型です。t.Go = sub.Go: これは、typedefされた型(t)のGo表現を、元の型(sub)のGo表現と全く同じにするという意味です。これにより、Goの型システム上で両者が互換性を持つようになります。- 変更前: この互換性を持たせる処理は、CgoがGoの定義ファイルを生成するモード(
-godefs)またはCの定義ファイルを生成するモード(-cdefs)の場合にのみ行われていました。 - 変更後:
isStructUnionClass(sub.Go)という新しい条件が追加されました。これは、元の型subのGo表現が、Cの構造体、共用体、またはクラスのタグ付き型(例:_Ctype_struct_S)である場合に、常にt.Go = sub.Goを実行することを意味します。 - この変更が、
typedef struct S T;の場合にC.TとC.struct_Sが互換性を持つようにする核心的な部分です。C.TのGo表現がC.struct_SのGo表現と同じになるため、Goの型システムはこれらを同じ型として扱います。
サイズ不明な型(t.Size < 0)の処理の変更
// 変更前:
// if _, ok := dtype.(*dwarf.TypedefType); !ok {
// t.Go = c.Opaque(0)
// }
// 変更後:
switch dt := dtype.(type) {
case *dwarf.TypedefType:
// ok
case *dwarf.StructType:
if dt.StructName != "" {
break
}
t.Go = c.Opaque(0)
default:
t.Go = c.Opaque(0)
}
t.Size < 0: これは、Goの型に変換された後の型が、そのサイズが不明であることを示します。これは通常、不完全型や、Go側で具体的なサイズを持つ必要がない型(ポインタとしてのみ扱われる型など)に適用されます。c.Opaque(0): これはGoの[0]byte型を生成します。サイズが不明な型をGoで表現する際の一般的な方法です。- 変更前:
typedef型でない限り、サイズ不明な型はすべて[0]byteとして扱われていました。 - 変更後:
switch文が導入され、より詳細な条件分岐が行われます。*dwarf.TypedefTypeの場合: 何もせず、既存のGo表現を維持します。これは、typedefされた不完全型が、元の不完全型と同じGo表現を持つようにする上記の変更と連携します。*dwarf.StructTypeの場合: もし構造体に名前(dt.StructName != "")があれば、breakして既存のGo表現を維持します。これは、タグ付きの不完全構造体(例:struct S;)が[0]byteにならないようにします。名前がない構造体(匿名構造体)の場合はc.Opaque(0)となります。- それ以外の場合:
c.Opaque(0)となります。
- この変更により、
typedefされた不完全型や、タグ付きの不完全構造体が、Go側でより適切にその名前を保持し、[0]byteに一律に変換されることを防ぎます。
func isStructUnionClass(x ast.Expr) bool 関数の追加
func isStructUnionClass(x ast.Expr) bool {
id, ok := x.(*ast.Ident)
if !ok {
return false
}
name := id.Name
return strings.HasPrefix(name, "_Ctype_struct_") ||
strings.HasPrefix(name, "_Ctype_union_") ||
strings.HasPrefix(name, "_Ctype_class_")
}
- このヘルパー関数は、GoのASTノード
xが識別子(ast.Ident)であり、その名前がCgoが生成するCの構造体、共用体、またはクラスの型名(_Ctype_struct_、_Ctype_union_、_Ctype_class_で始まるもの)であるかどうかをチェックします。 - この関数は、上記の
case *dwarf.TypedefType:ブロックで利用され、typedefされた型が元のCの構造体、共用体、クラス型と互換性を持つべきかどうかを判断するために使用されます。
これらの変更は、CgoがC言語の複雑な型システム(特にtypedefと不完全型)をGoの型システムにマッピングする際の精度と柔軟性を向上させるものです。これにより、Go 1.3で導入された厳密な型チェックの原則を維持しつつ、既存のCgoコードの後方互換性を確保するという、実用的なバランスが取られています。
関連リンク
- Go GitHub Commit: https://github.com/golang/go/commit/0782ee3ad57a21bd3566f20e76e4e453613e7a23
- Go Code Review: https://golang.org/cl/98580046
- Go Issue 7786: コミットメッセージに
Fixes #7786と記載されています。これは、Go 1.3での型厳密化によって発生した互換性問題を追跡するためのIssueであると推測されます。 - Go Issue 7409: コミットメッセージに
CL 76450043, which fixed issue 7409と記載されています。これは、Go 1.3で不完全型の厳密な区別を導入した元の変更に関連するIssueであると推測されます。 - Go 1.3 Release Notes (doc/go1.3.html): このコミットによって更新されたドキュメントは、Go 1.3のリリースノートの一部として、Cgoの型変換に関するこの変更の背景と影響を説明しています。
参考にした情報源リンク
- Go言語の公式ソースコード(特に
src/cmd/cgo/gcc.go、misc/cgo/test/issue7786.go、doc/go1.3.html) - コミットメッセージ自体
- C言語の
typedefと不完全型に関する一般的な知識 - Go言語のCgoに関する一般的な知識
- Dwarfデバッグ情報に関する一般的な知識
(注: コミットメッセージに記載されているCL 76450043およびIssue 7409については、Goの公開リポジトリや一般的な検索では直接関連する情報を見つけることができませんでした。これらは内部的なトラッキング番号であるか、非常に古い情報である可能性があります。したがって、本解説ではコミットメッセージの文脈からその内容を推測し、Goのソースコードの変更点から技術的詳細を導き出しています。)