[インデックス 13005] ファイルの概要
このコミットは、Go言語のcgo
ツールにおける型名の衝突問題を解決するためのものです。具体的には、Goの組み込み型(int
, uint
, float
など)がC言語にエクスポートされる際に生成されるC側の型名が、ユーザーが定義したCの型名と衝突する可能性があった問題を修正しています。この修正により、cgo
が生成するCの型名にGo
プレフィックスを付与することで、名前空間の衝突を回避し、より堅牢な相互運用性を実現しています。
コミット
commit 82e30c681c5fcf9957c7f5d17c90b800ecd1a85e
Author: Ian Lance Taylor <iant@golang.org>
Date: Tue May 1 09:04:13 2012 -0700
cgo: rename C names for Go types to avoid conflicting with package
Fixes #3371.
R=rsc, bsiegert, r, mtj, iant
CC=golang-dev
https://golang.org/cl/6131060
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/82e30c681c5fcf9957c7f5d17c90b800ecd1a85e
元コミット内容
このコミットの元の内容は以下の通りです。
cgo: rename C names for Go types to avoid conflicting with package
Fixes #3371.
R=rsc, bsiegert, r, mtj, iant
CC=golang-dev
https://golang.org/cl/6131060
これは、cgo
がGoの型に対して生成するCの型名を変更し、既存のCパッケージ内の名前との衝突を避けることを目的としています。特に、Goのint64
やuint64
といった型がCにエクスポートされる際に、Cのライブラリ(例: OpenCV)が独自にint64
/uint64
を定義している場合にビルドエラーが発生するという問題(Issue #3371)を解決します。
変更の背景
この変更の背景には、Go言語とC言語を相互に呼び出すためのツールであるcgo
の設計上の課題がありました。cgo
は、GoのコードからCの関数を呼び出したり、CのコードからGoの関数を呼び出したりする際に、GoとCの型システム間のマッピングを行います。
Goの組み込み型(例: int
, uint
, float32
, complex64
など)は、C言語の対応する型に変換されます。しかし、この変換において、cgo
が生成するC側の型名が、ユーザーがCのコードで既に定義している型名と偶然にも同じになってしまうという問題が発生していました。
具体的には、Goのint64
やuint64
といった型がCにエクスポートされる際に、cgo
はこれらをCのlong long
やunsigned long long
といったプリミティブ型にマッピングしていました。しかし、一部のCライブラリ(例えば、Issue #3371で言及されているOpenCVなど)は、独自のint64
やuint64
型をtypedef
などで定義している場合があります。
このような状況下で、cgo
が生成したCのヘッダーファイルと、ユーザーがインクルードしたCライブラリのヘッダーファイルが同じコンパイル単位に含まれると、同じ型名が複数回定義されることになり、コンパイラが「重複定義」のエラーを報告し、ビルドが失敗していました。
この問題は、特に大規模なCライブラリをGoプロジェクトに組み込む際に顕著になり、開発者の生産性を著しく低下させていました。このコミットは、この型名の衝突という根本的な問題を解決し、cgo
の堅牢性と使いやすさを向上させることを目的としています。
前提知識の解説
このコミットを理解するためには、以下の前提知識が必要です。
Go言語のcgo
cgo
は、GoプログラムからC言語のコードを呼び出したり、C言語のコードからGoの関数を呼び出したりするためのGoのツールです。Goのソースファイル内に特別なコメントブロック(import "C"
の直前のコメント)を使ってCのコードを記述することで、GoとCの相互運用を可能にします。
cgo
は、Goの型とCの型を自動的にマッピングしますが、その際にGoの型に対応するCの型名を生成します。例えば、Goのint
はCのint
に、Goのfloat64
はCのdouble
にマッピングされます。
型のエイリアスとtypedef
C言語では、typedef
キーワードを使って既存の型に新しい名前(エイリアス)を付けることができます。例えば、typedef long long int64;
とすることで、long long
型にint64
という新しい名前を付けることができます。これは、異なるプラットフォーム間での型の互換性を保つためや、コードの可読性を高めるためによく用いられます。
名前空間の衝突
プログラミングにおいて、名前空間の衝突(Name Collision)とは、異なるモジュールやライブラリで同じ名前が定義されている場合に発生する問題です。これにより、コンパイラやリンカがどの定義を使用すべきか判断できなくなり、エラーが発生します。今回の問題は、cgo
が生成するCの型名と、ユーザーがインクルードするCライブラリが定義する型名が衝突することによって引き起こされていました。
Goの組み込み型とCの対応する型
Go言語には、bool
, byte
, int
, uint
, rune
, int8
, uint8
, int16
, uint16
, int32
, uint32
, int64
, uint64
, float32
, float64
, complex64
, complex128
などの組み込み型があります。これらはcgo
によってCの対応する型にマッピングされます。
Goの型 | 従来のCの対応型 |
---|---|
bool | uchar |
byte | uchar |
int | int |
uint | uint |
rune | int |
int8 | schar |
uint8 | uchar |
int16 | short |
uint16 | ushort |
int32 | int |
uint32 | uint |
int64 | int64 |
uint64 | uint64 |
float32 | float |
float64 | double |
complex64 | __complex float |
complex128 | __complex double |
uintptr | uintptr |
string | GoString |
このコミットでは、上記の「従来のCの対応型」の一部が、Go
プレフィックスを持つ新しい型名に変更されます。
技術的詳細
このコミットの技術的な核心は、cgo
がGoの型をCの型にマッピングする際に使用する型名を変更することにあります。具体的には、src/cmd/cgo/out.go
ファイル内のgoTypes
マップとgccExportHeaderProlog
定数が変更されています。
goTypes
マップの変更
goTypes
マップは、Goの組み込み型とそれに対応するCの型定義を関連付ける役割を担っています。このマップでは、Goの型名(文字列)をキーとして、Type
構造体のポインタを値として保持しています。Type
構造体には、型のサイズ、アライメント、そしてC言語での表現(C
フィールド)が含まれています。
変更前は、int64
がint64
に、uint64
がuint64
に直接マッピングされていました。また、bool
やbyte
はuchar
に、int
はint
に、uint
はuint
に、rune
はint
に、int8
はschar
に、uint8
はuchar
に、int16
はshort
に、uint16
はushort
に、int32
はint
に、uint32
はuint
に、float32
はfloat
に、float64
はdouble
に、complex64
は__complex float
に、complex128
は__complex double
にマッピングされていました。
このコミットでは、これらのC側の型名にGo
プレフィックスが追加されました。例えば、int
はGoInt
に、uint
はGoUint
に、int64
はGoInt64
に、uint64
はGoUint64
に、float32
はGoFloat32
に、complex64
はGoComplex64
に、uintptr
はGoUintptr
に変更されています。これにより、cgo
が生成するCの型名が、一般的なCライブラリで定義されている型名と衝突する可能性が大幅に低減されます。
gccExportHeaderProlog
定数の変更
gccExportHeaderProlog
定数は、cgo
が生成するCのヘッダーファイルの冒頭に挿入されるCコードのスニペットです。このスニペットには、Goの型に対応するCの型をtypedef
で定義する部分が含まれています。
変更前は、typedef unsigned int uint;
やtypedef long long int64;
のように、一般的なCの型名がそのまま使用されていました。
変更後には、これらのtypedef
定義もGo
プレフィックスを持つ新しい型名に変更されました。例えば、typedef int GoInt;
、typedef unsigned int GoUint;
、typedef long long GoInt64;
、typedef unsigned long long GoUint64;
などが追加されています。これにより、cgo
が生成するCのヘッダーファイル内で定義される型名が、他のCライブラリの型名と衝突しないように明示的に区別されます。
この変更は、cgo
が生成するCのコードの互換性を向上させ、GoとCの相互運用における型名の衝突問題を根本的に解決します。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更箇所は、src/cmd/cgo/out.go
ファイル内の以下の2つのセクションです。
goTypes
マップの定義gccExportHeaderProlog
定数の定義
src/cmd/cgo/out.go
--- a/src/cmd/cgo/out.go
+++ b/src/cmd/cgo/out.go
@@ -736,25 +736,23 @@ func c(repr string, args ...interface{}) *TypeRepr {
// Map predeclared Go types to Type.
var goTypes = map[string]*Type{
- "bool": {Size: 1, Align: 1, C: c("uchar")},
- "byte": {Size: 1, Align: 1, C: c("uchar")},
- "int": {Size: 4, Align: 4, C: c("int")},
- "uint": {Size: 4, Align: 4, C: c("uint")},
- "rune": {Size: 4, Align: 4, C: c("int")},
- "int8": {Size: 1, Align: 1, C: c("schar")},
- "uint8": {Size: 1, Align: 1, C: c("uchar")},
- "int16": {Size: 2, Align: 2, C: c("short")},
- "uint16": {Size: 2, Align: 2, C: c("ushort")},
- "int32": {Size: 4, Align: 4, C: c("int")},
- "uint32": {Size: 4, Align: 4, C: c("uint")},
- "int64": {Size: 8, Align: 8, C: c("int64")},
- "uint64": {Size: 8, Align: 8, C: c("uint64")},
- "float": {Size: 4, Align: 4, C: c("float")},
- "float32": {Size: 4, Align: 4, C: c("float")},
- "float64": {Size: 8, Align: 8, C: c("double")},
- "complex": {Size: 8, Align: 8, C: c("__complex float")},
- "complex64": {Size: 8, Align: 8, C: c("__complex float")},
- "complex128": {Size: 16, Align: 16, C: c("__complex double")},
+ "bool": {Size: 1, Align: 1, C: c("GoUint8")},
+ "byte": {Size: 1, Align: 1, C: c("GoUint8")},
+ "int": {Size: 4, Align: 4, C: c("GoInt")},
+ "uint": {Size: 4, Align: 4, C: c("GoUint")},
+ "rune": {Size: 4, Align: 4, C: c("GoInt32")},
+ "int8": {Size: 1, Align: 1, C: c("GoInt8")},
+ "uint8": {Size: 1, Align: 1, C: c("GoUint8")},
+ "int16": {Size: 2, Align: 2, C: c("GoInt16")},
+ "uint16": {Size: 2, Align: 2, C: c("GoUint16")},
+ "int32": {Size: 4, Align: 4, C: c("GoInt32")},
+ "uint32": {Size: 4, Align: 4, C: c("GoUint32")},
+ "int64": {Size: 8, Align: 8, C: c("GoInt64")},
+ "uint64": {Size: 8, Align: 8, C: c("GoUint64")},
+ "float32": {Size: 4, Align: 4, C: c("GoFloat32")},
+ "float64": {Size: 8, Align: 8, C: c("GoFloat64")},
+ "complex64": {Size: 8, Align: 8, C: c("GoComplex64")},
+ "complex128": {Size: 16, Align: 16, C: c("GoComplex128")},
}
// Map an ast type to a Type.
@@ -799,7 +797,7 @@ func (p *Package) cgoType(e ast.Expr) *Type {
return def
}
if t.Name == "uintptr" {
- return &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("uintptr")}
+ return &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("GoUintptr")}
}
if t.Name == "string" {
return &Type{Size: p.PtrSize + 4, Align: p.PtrSize, C: c("GoString")}
@@ -930,13 +928,21 @@ Slice GoBytes(char *p, int n) {
`
const gccExportHeaderProlog = `
-typedef unsigned int uint;
-typedef signed char schar;
-typedef unsigned char uchar;
-typedef unsigned short ushort;
-typedef long long int64;
-typedef unsigned long long uint64;
-typedef __SIZE_TYPE__ uintptr;
+typedef int GoInt;
+typedef unsigned int GoUint;
+typedef signed char GoInt8;
+typedef unsigned char GoUint8;
+typedef short GoInt16;
+typedef unsigned short GoUint16;
+typedef int GoInt32;
+typedef unsigned int GoUint32;
+typedef long long GoInt64;
+typedef unsigned long long GoUint64;
+typedef __SIZE_TYPE__ GoUintptr;
+typedef float GoFloat32;
+typedef double GoFloat64;
+typedef __complex float GoComplex64;
+typedef __complex double GoComplex128;
typedef struct { char *p; int n; } GoString;
typedef void *GoMap;
コアとなるコードの解説
goTypes
マップの変更
goTypes
マップは、Goの組み込み型がC言語にどのようにマッピングされるかを定義しています。このマップの各エントリは、Goの型名(文字列)と、その型がC言語でどのように表現されるかを示すType
構造体で構成されています。
変更前は、C: c("uchar")
やC: c("int")
のように、Cのプリミティブ型名が直接指定されていました。しかし、これらの名前は一般的なCライブラリでも使用される可能性があり、名前の衝突を引き起こしていました。
変更後には、C: c("GoUint8")
やC: c("GoInt")
のように、すべてのCの型名にGo
プレフィックスが追加されました。これにより、cgo
がGoの型をCにエクスポートする際に生成する型名が、Go言語専用の名前空間を持つことになり、他のCライブラリとの名前の衝突が回避されます。
例えば、Goのint
型は、C側ではGoInt
として扱われるようになります。これにより、もしCのライブラリが独自にint
という型を定義していたとしても、GoInt
とは異なる名前であるため、衝突が発生しなくなります。
gccExportHeaderProlog
定数の変更
gccExportHeaderProlog
定数は、cgo
が生成するCのヘッダーファイル(通常は_cgo_export.h
のような名前)の冒頭に挿入されるCコードの文字列です。この部分では、Goの型に対応するCの型をtypedef
を使って定義しています。
変更前は、typedef unsigned int uint;
やtypedef long long int64;
のように、一般的なCの型名がtypedef
されていました。これは、goTypes
マップでのマッピングと整合性がありましたが、同時に名前の衝突の原因でもありました。
変更後には、このgccExportHeaderProlog
内のtypedef
定義も、Go
プレフィックスを持つ新しい型名に変更されました。例えば、typedef int GoInt;
、typedef unsigned int GoUint;
、typedef long long GoInt64;
などが追加されています。
この変更により、cgo
が生成するCのヘッダーファイル内で定義されるすべてのGo関連の型が、Go
プレフィックスを持つ一貫した命名規則に従うことになります。これは、cgo
によって生成されるCのコードが、他のCライブラリと共存する際の互換性を大幅に向上させるための重要な変更です。
これらの変更は、GoとCの相互運用における型名の衝突という具体的な問題を解決し、cgo
の信頼性と使いやすさを向上させることに貢献しています。
関連リンク
- Go言語の
cgo
ドキュメント: https://pkg.go.dev/cmd/cgo - Go Issue #3371: https://github.com/golang/go/issues/3371
- Gerrit Change 6131060: https://golang.org/cl/6131060
参考にした情報源リンク
- Go Issue #3371のGitHubページ: https://github.com/golang/go/issues/3371
- Stack Overflowの関連議論(cgoの課題について): https://stackoverflow.com/questions/tagged/go-cgo
- Go言語の公式ブログやドキュメント(cgoに関する情報)
- C言語の
typedef
に関する一般的な情報源 - Go言語の型システムに関する一般的な情報源