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

[インデックス 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.Pointeruintptr は相互に変換可能です。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言語の printfscanf に似た機能を提供し、様々な型の値を整形して文字列として出力したり、文字列から値をパースしたりすることができます。

主要な関数には fmt.Print, fmt.Println, fmt.Printf などがあります。Printf 関数はフォーマット指定子(例: %d で整数、%s で文字列、%v でデフォルトフォーマット)を使用して出力形式を制御します。このコミットでは、uintptr 型の値を適切にフォーマットして出力するためのロジックが fmt パッケージに追加されています。特に、ポインタアドレスは通常16進数で表現されるため、そのためのフォーマットが重要になります。

技術的詳細

このコミットの技術的な核心は、Go言語の型システム、リフレクションメカニズム、および標準出力フォーマット機能に uintptr 型を完全に統合することにあります。

  1. reflect パッケージへの UintptrKind の追加:

    • src/lib/reflect/type.goUintptrKind という新しい定数が追加されました。これは reflect.Kind 列挙型の一部となり、uintptr 型をリフレクションで識別するための基本的な分類を提供します。
    • NewBasicType 関数を使用して、"uintptr" という名前、UintptrKind、そしてシステム依存のサイズ(ここでは一時的に8バイトと仮定)を持つ Uintptr 型が定義されました。これは、reflect パッケージが uintptr 型の静的な情報を取得できるようにするための基盤となります。
    • init() 関数内の types マップと basicstub マップに "uintptr"Uintptr のマッピングが追加され、文字列名から reflect.Type オブジェクトへのルックアップが可能になりました。
  2. reflect パッケージにおける UintptrValue インターフェースと実装の追加:

    • src/lib/reflect/value.goUintptrValue インターフェースが定義されました。このインターフェースは、uintptr 型の値をリフレクションで操作するための Kind(), Get(), Set() メソッドを規定します。
    • UintptrValueStruct という構造体が定義され、UintptrValue インターフェースの実装を提供します。この構造体は、uintptr 型の実際の値へのポインタ(addr)を保持します。
    • UintptrCreator 関数が追加されました。これは、reflect.ValueOf() のような関数が uintptr 型の値を reflect.Value としてラップする際に、UintptrValueStruct のインスタンスを生成するためのファクトリ関数として機能します。
    • creator マップに UintptrKind&UintptrCreator のマッピングが追加され、reflect.NewValueAddr 関数が uintptr 型の値を正しく処理できるようになりました。
  3. 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.UintptrKindValue が渡された場合にも、その内部の 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 オブジェクトが作成されました。8uintptr のサイズ(バイト単位)を示しており、当時の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を通じて操作・検査できるようになりました。

関連リンク

参考にした情報源リンク