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

[インデックス 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; のように記述します。この場合、Tint型のメンバ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; この場合、Pointint xint yを持つ構造体の新しい型名となりますが、structキーワードの後にタグは指定されていません。このような構造体を匿名構造体と呼びます。

  • typedef: C言語のキーワードで、既存のデータ型に新しい別名(エイリアス)を定義するために使用されます。これにより、コードの可読性を向上させたり、複雑な型宣言を簡略化したりできます。 例: typedef unsigned int UINT;unsigned intUINTという別名を定義) 構造体と組み合わせて、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つのファイルで行われています。

  1. 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を呼び出し、コンパイルが成功することを確認します。

  2. 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 cgo untagged struct typedef に関するWeb検索結果
  • Go cgo gcc.go type conversion に関するWeb検索結果
  • Go言語のCgoに関する公式ドキュメントおよび関連するコミュニティの議論