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

[インデックス 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のint64uint64といった型が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のint64uint64といった型がCにエクスポートされる際に、cgoはこれらをCのlong longunsigned long longといったプリミティブ型にマッピングしていました。しかし、一部のCライブラリ(例えば、Issue #3371で言及されているOpenCVなど)は、独自のint64uint64型を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の対応型
booluchar
byteuchar
intint
uintuint
runeint
int8schar
uint8uchar
int16short
uint16ushort
int32int
uint32uint
int64int64
uint64uint64
float32float
float64double
complex64__complex float
complex128__complex double
uintptruintptr
stringGoString

このコミットでは、上記の「従来のCの対応型」の一部が、Goプレフィックスを持つ新しい型名に変更されます。

技術的詳細

このコミットの技術的な核心は、cgoがGoの型をCの型にマッピングする際に使用する型名を変更することにあります。具体的には、src/cmd/cgo/out.goファイル内のgoTypesマップとgccExportHeaderProlog定数が変更されています。

goTypesマップの変更

goTypesマップは、Goの組み込み型とそれに対応するCの型定義を関連付ける役割を担っています。このマップでは、Goの型名(文字列)をキーとして、Type構造体のポインタを値として保持しています。Type構造体には、型のサイズ、アライメント、そしてC言語での表現(Cフィールド)が含まれています。

変更前は、int64int64に、uint64uint64に直接マッピングされていました。また、boolbyteucharに、intintに、uintuintに、runeintに、int8scharに、uint8ucharに、int16shortに、uint16ushortに、int32intに、uint32uintに、float32floatに、float64doubleに、complex64__complex floatに、complex128__complex doubleにマッピングされていました。

このコミットでは、これらのC側の型名にGoプレフィックスが追加されました。例えば、intGoIntに、uintGoUintに、int64GoInt64に、uint64GoUint64に、float32GoFloat32に、complex64GoComplex64に、uintptrGoUintptrに変更されています。これにより、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つのセクションです。

  1. goTypesマップの定義
  2. 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の信頼性と使いやすさを向上させることに貢献しています。

関連リンク

参考にした情報源リンク