[インデックス 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言語の設計思想と歴史的背景の理解に役立つ)