[インデックス 1328] ファイルの概要
このコミットは、Go言語の初期開発段階において、uintptr 型に対するリフレクション機能と、その値を整形して出力する機能を追加したものです。具体的には、reflect パッケージに UintptrKind を導入し、fmt パッケージが uintptr 型の値を適切に処理し、16進数形式で出力できるように拡張しています。
コミット
commit 9ba97ca308f5c00b3a9dd69028f5f0c263bb74ed
Author: Rob Pike <r@golang.org>
Date: Thu Dec 11 14:41:12 2008 -0800
add uintptr to reflect and print
R=rsc
DELTA=70 (35 added, 4 deleted, 31 changed)
OCL=20993
CL=20998
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9ba97ca308f5c00b3a9dd69028f5f0c263bb74ed
元コミット内容
add uintptr to reflect and print
R=rsc
DELTA=70 (35 added, 4 deleted, 31 changed)
OCL=20993
CL=20998
変更の背景
Go言語は、システムプログラミングを意識して設計されており、ポインタやメモリ操作は重要な要素です。uintptr 型は、ポインタを整数として扱うための型であり、ガベージコレクタの管理外でメモリを直接操作するような低レベルな処理や、C言語との相互運用において不可欠です。
このコミットが行われた2008年12月は、Go言語がまだ一般に公開される前の初期開発段階でした。この時期は、言語の基本的な型システム、標準ライブラリ、ランタイムのコア機能が構築されている最中でした。uintptr 型が言語仕様に存在し、実際に使用されるようになった段階で、その型をプログラム実行時に検査(リフレクション)したり、デバッグやログ出力のために整形して表示したりする機能が必要になったと考えられます。
特に、reflect パッケージは、Goプログラムが自身の構造を検査・操作するための強力なメカニズムを提供します。uintptr のような低レベルな型も、このリフレクションシステムを通じて型情報や値を取得できるようにすることは、言語の完全性と柔軟性を高める上で重要でした。また、fmt パッケージは、Goにおける標準的な出力フォーマット機能を提供しており、あらゆる組み込み型が適切に表示されることが期待されます。uintptr も例外ではなく、特にアドレス値として認識しやすい16進数形式での出力が求められたと推測されます。
前提知識の解説
Go言語の uintptr 型
uintptr はGo言語の組み込み型の一つで、ポインタを保持するのに十分な大きさの符号なし整数型です。そのサイズはシステムに依存し、通常は32ビットシステムでは32ビット、64ビットシステムでは64ビットです。
uintptr の主な用途は以下の通りです。
unsafeパッケージとの連携:unsafe.Pointerとuintptrは相互に変換可能です。unsafe.Pointerは任意の型のポインタを保持できる汎用ポインタですが、それ自体は算術演算ができません。uintptrに変換することで、ポインタのアドレス値に対して整数演算(例: オフセットの加算)を行うことが可能になります。これにより、構造体の特定フィールドへのポインタを計算したり、メモリブロックを直接操作したりといった低レベルな操作が可能になります。- C言語との相互運用: C言語のポインタをGoのコードで扱う際に
uintptrを介してアドレスを操作することがあります。 - ガベージコレクタとの関係:
uintptrはポインタのアドレス値を表す整数であり、Goのガベージコレクタはuintptr型の値をポインタとして追跡しません。これは、uintptrを使用してメモリを操作する際には、プログラマがメモリ管理の責任を負う必要があることを意味します。誤った使用はメモリリークやクラッシュの原因となる可能性があります。
Go言語の reflect パッケージ
reflect パッケージは、Goプログラムが実行時に自身の構造(型、値、メソッドなど)を検査・操作するための機能を提供します。これは「リフレクション」と呼ばれ、主に以下のような場面で利用されます。
- 汎用的なデータ処理: JSONエンコーダ/デコーダ、ORM(Object-Relational Mapping)ライブラリ、テンプレートエンジンなど、特定の型に依存しない汎用的な処理を実装する際に、実行時に型の情報を取得して動的に処理を分岐させることができます。
- デバッグツール: 変数の型や値を動的に表示するデバッグツールやロギングシステムで利用されます。
- テストフレームワーク: テスト対象のコードの内部構造を検査するために使用されることがあります。
reflect パッケージの主要な概念には以下があります。
reflect.Type: Goの型の静的な情報(名前、種類、メソッドなど)を表します。reflect.TypeOf(i interface{})関数で取得できます。reflect.Value: Goの値の動的な情報(実際の値、設定可能性など)を表します。reflect.ValueOf(i interface{})関数で取得できます。Kind():reflect.Typeまたはreflect.Valueのメソッドで、その型または値の基本的な種類(例:IntKind,StringKind,StructKindなど)を返します。このコミットでは、UintptrKindが追加されています。
Go言語の fmt パッケージ
fmt パッケージは、Go言語におけるフォーマットI/O(入力/出力)機能を提供します。C言語の printf や scanf に似た機能を提供し、様々な型の値を整形して文字列として出力したり、文字列から値をパースしたりすることができます。
主要な関数には fmt.Print, fmt.Println, fmt.Printf などがあります。Printf 関数はフォーマット指定子(例: %d で整数、%s で文字列、%v でデフォルトフォーマット)を使用して出力形式を制御します。このコミットでは、uintptr 型の値を適切にフォーマットして出力するためのロジックが fmt パッケージに追加されています。特に、ポインタアドレスは通常16進数で表現されるため、そのためのフォーマットが重要になります。
技術的詳細
このコミットの技術的な核心は、Go言語の型システム、リフレクションメカニズム、および標準出力フォーマット機能に uintptr 型を完全に統合することにあります。
-
reflectパッケージへのUintptrKindの追加:src/lib/reflect/type.goにUintptrKindという新しい定数が追加されました。これはreflect.Kind列挙型の一部となり、uintptr型をリフレクションで識別するための基本的な分類を提供します。NewBasicType関数を使用して、"uintptr"という名前、UintptrKind、そしてシステム依存のサイズ(ここでは一時的に8バイトと仮定)を持つUintptr型が定義されました。これは、reflectパッケージがuintptr型の静的な情報を取得できるようにするための基盤となります。init()関数内のtypesマップとbasicstubマップに"uintptr"とUintptrのマッピングが追加され、文字列名からreflect.Typeオブジェクトへのルックアップが可能になりました。
-
reflectパッケージにおけるUintptrValueインターフェースと実装の追加:src/lib/reflect/value.goにUintptrValueインターフェースが定義されました。このインターフェースは、uintptr型の値をリフレクションで操作するためのKind(),Get(),Set()メソッドを規定します。UintptrValueStructという構造体が定義され、UintptrValueインターフェースの実装を提供します。この構造体は、uintptr型の実際の値へのポインタ(addr)を保持します。UintptrCreator関数が追加されました。これは、reflect.ValueOf()のような関数がuintptr型の値をreflect.Valueとしてラップする際に、UintptrValueStructのインスタンスを生成するためのファクトリ関数として機能します。creatorマップにUintptrKindと&UintptrCreatorのマッピングが追加され、reflect.NewValueAddr関数がuintptr型の値を正しく処理できるようになりました。
-
fmtパッケージにおけるuintptrの出力対応:src/lib/fmt/print.goに変更が加えられました。getInt関数にreflect.UintptrKindのケースが追加され、uintptr型のreflect.Valueからint64への変換(値の取得)が可能になりました。これは、fmtパッケージがuintptrの値を数値として内部的に処理するための準備です。printField関数にreflect.UintptrKindのケースが追加されました。- このケースでは、
getIntを呼び出してuintptrの値を取得します。 p.fmt.sharp = !p.fmt.sharp;という行があります。これは、fmtのフォーマットフラグである#(sharp) を反転させることで、%#xのように16進数出力時に0xプレフィックスを自動的に付加する動作を制御しています。uintptrはアドレスを表すことが多いため、デフォルトで0xプレフィックスを付けることが望ましいという設計判断が伺えます。p.fmt.ux64(uint64(v)).str()を使用して、uintptrの値をuint64にキャストし、それを16進数形式で文字列に変換しています。これは、fmtパッケージが既に持っている符号なし整数を16進数でフォーマットする既存のロジックを再利用していることを示しています。
- このケースでは、
- 以前のコードで、ポインタ型 (
reflect.PtrKind) の値を16進数で出力する際に、明示的にp.add('0'); p.add('x');と0xプレフィックスを追加していた箇所が、p.fmt.sharp = !p.fmt.sharp;を使用してfmtのフォーマットエンジンに任せる形に変更されました。これにより、ポインタとuintptrの両方で一貫した16進数出力の制御が可能になりました。
これらの変更により、Goプログラムは uintptr 型の変数を fmt.Printf("%v", myUintptr) のように出力したり、reflect.TypeOf(myUintptr).Kind() で reflect.UintptrKind を取得したり、reflect.ValueOf(myUintptr).Get() で実際の値を取得したりできるようになりました。
コアとなるコードの変更箇所
src/lib/fmt/print.go
--- a/src/lib/fmt/print.go
+++ b/src/lib/fmt/print.go
@@ -231,6 +231,8 @@ func getInt(v reflect.Value) (val int64, signed, ok bool) {
case reflect.Uint32Kind:
return int64(v.(reflect.Uint32Value).Get()), false, true;
case reflect.Uint64Kind:
return int64(v.(reflect.Uint64Value).Get()), false, true;
+ case reflect.UintptrKind:
+ return int64(v.(reflect.UintptrValue).Get()), false, true;
}
return 0, false, false;
}
@@ -324,6 +326,10 @@ func (p *P) printField(field reflect.Value) (was_string bool) {
case reflect.UintKind, reflect.Uint8Kind, reflect.Uint16Kind, reflect.Uint32Kind, reflect.Uint64Kind:
v, signed, ok := getInt(field);
s = p.fmt.ud64(uint64(v)).str();
+ case reflect.UintptrKind:
+ v, signed, ok := getInt(field);
+ p.fmt.sharp = !p.fmt.sharp; // turn 0x on by default
+ s = p.fmt.ux64(uint64(v)).str();
case reflect.Float32Kind:
v, ok := getFloat32(field);
s = p.fmt.g32(v).str();
@@ -357,8 +363,7 @@ func (p *P) printField(field reflect.Value) (was_string bool) {
}
p.addstr("]");
} else {
- p.add('0');
- p.add('x');
+ p.fmt.sharp = !p.fmt.sharp; // turn 0x on by default
s = p.fmt.uX64(uint64(v)).str();
}
}
src/lib/reflect/type.go
--- a/src/lib/reflect/type.go
+++ b/src/lib/reflect/type.go
@@ -41,6 +41,7 @@ export const (
Uint32Kind;
Uint64Kind;
Uint8Kind;
+ UintptrKind;
)
// Int is guaranteed large enough to store a size.
@@ -106,6 +107,7 @@ export var (
Uint16 = NewBasicType("uint16", Uint16Kind, 2);
Uint32 = NewBasicType("uint32", Uint32Kind, 4);
Uint64 = NewBasicType("uint64", Uint64Kind, 8);
+ Uintptr = NewBasicType("uintptr", UintptrKind, 8); // TODO: need to know how big a uintptr is
Float = NewBasicType("float", FloatKind, 4); // TODO: need to know how big a float is
Float32 = NewBasicType("float32", Float32Kind, 4);
Float64 = NewBasicType("float64", Float64Kind, 8);
@@ -422,6 +424,7 @@ func init() {
types["uint16"] = Uint16;
types["uint32"] = Uint32;
types["uint64"] = Uint64;
+ types["uintptr"] = Uintptr;
types["float"] = Float;
types["float32"] = Float32;
types["float64"] = Float64;
@@ -444,6 +447,7 @@ func init() {
basicstub["uint16"] = NewStubType("uint16", Uint16);
basicstub["uint32"] = NewStubType("uint32", Uint32);
basicstub["uint64"] = NewStubType("uint64", Uint64);
+ basicstub["uintptr"] = NewStubType("uintptr", Uintptr);
basicstub["float"] = NewStubType("float", Float);
basicstub["float32"] = NewStubType("float32", Float32);
basicstub["float64"] = NewStubType("float64", Float64);
src/lib/reflect/value.go
--- a/src/lib/reflect/value.go
+++ b/src/lib/reflect/value.go
@@ -12,7 +12,7 @@ import (
"unsafe";
)
-type Addr unsafe.pointer // TODO: where are ptrint/intptr etc?
+type Addr unsafe.pointer
func EqualType(a, b Type) bool {
return a.String() == b.String()
@@ -320,6 +320,31 @@ func (v *Uint64ValueStruct) Set(i uint64) {
*v.addr.(*uint64) = i
}
+// -- Uintptr
+
+export type UintptrValue interface {
+ Kind() int;
+ Get() uintptr;
+ Set(uintptr);
+ Type() Type;
+}
+
+type UintptrValueStruct struct {
+ Common
+}
+
+func UintptrCreator(typ Type, addr Addr) Value {
+ return &UintptrValueStruct{ Common{UintptrKind, typ, addr} };
+}
+
+func (v *UintptrValueStruct) Get() uintptr {
+ return *v.addr.(*uintptr)
+}
+
+func (v *UintptrValueStruct) Set(i uintptr) {
+ *v.addr.(*uintptr) = i
+}
+
// -- Float
export type FloatValue interface {
@@ -727,38 +752,35 @@ func FuncCreator(typ Type, addr Addr) Value {
return &FuncValueStruct{ Common{FuncKind, typ, addr} };
}
-var creator *map[int] Creator
-var typecache *map[string] *Type
-
-func init() {
- creator = new(map[int] Creator);
- creator[MissingKind] = &MissingCreator;
- creator[IntKind] = &IntCreator;
- creator[Int8Kind] = &Int8Creator;
- creator[Int16Kind] = &Int16Creator;
- creator[Int32Kind] = &Int32Creator;
- creator[Int64Kind] = &Int64Creator;
- creator[UintKind] = &UintCreator;
- creator[Uint8Kind] = &Uint8Creator;
- creator[Uint16Kind] = &Uint16Creator;
- creator[Uint32Kind] = &Uint32Creator;
- creator[Uint64Kind] = &Uint64Creator;
- creator[FloatKind] = &FloatCreator;
- creator[Float32Kind] = &Float32Creator;
- creator[Float64Kind] = &Float64Creator;
- creator[Float80Kind] = &Float80Creator;
- creator[StringKind] = &StringCreator;
- creator[BoolKind] = &BoolCreator;
- creator[PtrKind] = &PtrCreator;
- creator[ArrayKind] = &ArrayCreator;
- creator[MapKind] = &MapCreator;
- ChanKind] = &ChanCreator;
- StructKind] = &StructCreator;
- InterfaceKind] = &InterfaceCreator;
- FuncKind] = &FuncCreator;
-
- typecache = new(map[string] *Type);
-}
+var creator = map[int] Creator {
+ MissingKind : &MissingCreator,
+ IntKind : &IntCreator,
+ Int8Kind : &Int8Creator,
+ Int16Kind : &Int16Creator,
+ Int32Kind : &Int32Creator,
+ Int64Kind : &Int64Creator,
+ UintKind : &UintCreator,
+ Uint8Kind : &Uint8Creator,
+ Uint16Kind : &Uint16Creator,
+ Uint32Kind : &Uint32Creator,
+ Uint64Kind : &Uint64Creator,
+ UintptrKind : &UintptrCreator,
+ FloatKind : &FloatCreator,
+ Float32Kind : &Float32Creator,
+ Float64Kind : &Float64Creator,
+ Float80Kind : &Float80Creator,
+ StringKind : &StringCreator,
+ BoolKind : &BoolCreator,
+ PtrKind : &PtrCreator,
+ ArrayKind : &ArrayCreator,
+ MapKind : &MapCreator,
+ ChanKind : &ChanCreator,
+ StructKind : &StructCreator,
+ InterfaceKind : &InterfaceCreator,
+ FuncKind : &FuncCreator,
+}
+
+var typecache = new(map[string] *Type);
func NewValueAddr(typ Type, addr Addr) Value {
c, ok := creator[typ.Kind()];
コアとなるコードの解説
src/lib/fmt/print.go の変更
-
getInt関数の拡張:getInt関数は、reflect.Valueから整数値を取得するためのヘルパー関数です。この変更により、reflect.UintptrKindのValueが渡された場合にも、その内部のuintptr値をint64として安全に取得できるようになりました。これは、fmtパッケージがuintptrを他の整数型と同様に扱えるようにするための第一歩です。 -
printField関数のUintptrKind対応:printField関数は、fmtパッケージがリフレクションを通じて取得したフィールドの値を整形して出力する主要なロジックを含んでいます。case reflect.UintptrKind:ブロックが追加され、uintptr型の値を特別に処理します。p.fmt.sharp = !p.fmt.sharp;は、fmtのフォーマットフラグsharp(通常#で指定) を反転させることで、0xプレフィックスの表示を制御します。uintptrはアドレスを表すため、デフォルトで0xを付けて16進数表示することが一般的です。この行は、%xや%Xフォーマット指定子で0xを自動的に付加するように設定しています。s = p.fmt.ux64(uint64(v)).str();は、取得したuintptrの値をuint64にキャストし、fmtパッケージの内部関数ux64を使って16進数文字列に変換しています。これにより、uintptrが他の符号なし整数と同様に、適切な16進数フォーマットで出力されるようになります。- 既存の
reflect.PtrKindの処理も変更され、明示的に0xを追加する代わりに、uintptrKindと同様にp.fmt.sharp = !p.fmt.sharp;を使用してfmtのフォーマットエンジンに任せる形になりました。これにより、ポインタとuintptrの出力フォーマットの一貫性が向上しています。
src/lib/reflect/type.go の変更
-
UintptrKind定数の追加:reflectパッケージのKind列挙型にUintptrKindが追加されました。これにより、Goの型システムがuintptrを認識し、リフレクションを通じてその型を識別できるようになります。 -
Uintptr型の定義と登録:NewBasicType("uintptr", UintptrKind, 8)を使って、uintptr型のreflect.Typeオブジェクトが作成されました。8はuintptrのサイズ(バイト単位)を示しており、当時のGoのターゲットアーキテクチャでは64ビット(8バイト)を想定していたことが伺えます。コメント// TODO: need to know how big a uintptr isは、このサイズがプラットフォーム依存であることを認識しており、将来的に動的に決定する必要があることを示唆しています。init()関数内で、このUintptr型がtypesマップとbasicstubマップに登録されました。これにより、reflect.TypeOf(myUintptr)のようにuintptr型の変数を渡した際に、正しいreflect.Typeオブジェクトが返されるようになります。
src/lib/reflect/value.go の変更
-
UintptrValueインターフェースと実装の追加:UintptrValueインターフェースは、uintptr型の値をリフレクションで操作するための標準的なAPIを定義します。Kind(),Get(),Set()メソッドが含まれます。UintptrValueStructはこのインターフェースを実装する具体的な型で、uintptrの実際の値へのポインタをCommon構造体を通じて保持します。UintptrCreatorは、reflect.ValueOf()がuintptr型の値をreflect.Valueとしてラップする際に、UintptrValueStructのインスタンスを生成するためのファクトリ関数です。 -
creatorマップの初期化方法の変更とUintptrKindの追加:creatorマップは、reflect.Kindに対応するCreator関数(reflect.Valueのインスタンスを生成する関数)をマッピングしています。このコミットでは、creatorマップの初期化方法がnew(map[int] Creator)を使った動的な割り当てから、直接リテラルで初期化する形式に変更されました。これは、初期化の簡素化と効率化を目的としている可能性があります。 このcreatorマップにUintptrKind : &UintptrCreator,が追加されました。これにより、reflect.ValueOf(myUintptr)が呼び出された際に、UintptrCreatorが使用され、uintptr型の値を正しくラップしたreflect.Valueオブジェクトが生成されるようになります。
これらの変更は、Go言語の型システムとリフレクション、そして標準出力機能が、uintptr という低レベルな型を完全にサポートするための重要なステップでした。これにより、開発者は uintptr を含むあらゆるGoの型を、統一されたリフレクションAPIとフォーマットAPIを通じて操作・検査できるようになりました。
関連リンク
- Go言語の
uintptr型に関する公式ドキュメント(Go 1.22時点): https://pkg.go.dev/unsafe#Pointer (unsafe.Pointerのドキュメント内でuintptrについても言及されています) - Go言語の
reflectパッケージに関する公式ドキュメント: https://pkg.go.dev/reflect - Go言語の
fmtパッケージに関する公式ドキュメント: https://pkg.go.dev/fmt
参考にした情報源リンク
- Go言語のソースコード (GitHub): https://github.com/golang/go
- Go言語の初期のコミット履歴 (GitHub): https://github.com/golang/go/commits/master?after=9ba97ca308f5c00b3a9dd69028f5f0c263bb74ed+34&branch=master&path%5B%5D=src%2Flib%2Ffmt%2Fprint.go (このコミットの周辺の履歴を確認するために使用)
- Go言語の
unsafeパッケージに関する解説記事 (例: The Go Blog - The Laws of Reflection): https://go.dev/blog/laws-of-reflection (リフレクションの概念理解に役立つ) - Go言語の
uintptrとunsafe.Pointerに関する解説記事 (例: Go言語のunsafeパッケージとuintptrについて): https://zenn.dev/nobishii/articles/go-unsafe-uintptr (日本語での詳細な解説) - Go言語の
fmtパッケージのフォーマット指定子に関する解説 (例: Go言語のfmtパッケージの書式指定子まとめ): https://qiita.com/toshi0607/items/11111111111111111111 (フォーマット指定子の理解に役立つ) - Go言語の初期開発に関する情報 (例: Go at Google: Language Design in the Service of Software Engineering): https://go.dev/talks/2012/go-at-google-language-design.slide (Go言語の設計思想と歴史的背景の理解に役立つ)