[インデックス 1644] ファイルの概要
このコミットは、Go言語のreflect
パッケージにおけるポインタとインターフェースのサイズ計算方法を改善するものです。具体的には、ハードコードされたサイズ値(ptrsize = 8
など)を削除し、代わりにunsafe.Sizeof
関数を使用して実行時にこれらのサイズを動的に決定するように変更しています。これにより、異なるアーキテクチャやコンパイル環境においても、reflect
パッケージが正確なサイズ情報を提供するようになります。
コミット
commit 9526f3b841275529cb5c5b9c1889b9bc359634b2
Author: Rob Pike <r@golang.org>
Date: Sun Feb 8 10:16:32 2009 -0800
use unsafe.Sizeof
R=rsc
DELTA=9 (3 added, 3 deleted, 3 changed)
OCL=24640
CL=24653
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9526f3b841275529cb5c5b9c1889b9bc359634b2
元コミット内容
use unsafe.Sizeof
このコミットは、unsafe.Sizeof
を使用するように変更します。
変更の背景
Go言語の初期段階において、reflect
パッケージは型情報のランタイム表現を扱っていました。このパッケージが正確に機能するためには、ポインタやインターフェースといった基本的な型のメモリ上でのサイズを正確に知る必要がありました。
このコミット以前は、src/lib/reflect/type.go
内でptrsize
とinterfacesize
が以下のようにハードコードされていました。
var ptrsize int
var interfacesize int
func init() {
ptrsize = 8; // TODO: compute this
interfacesize = 2*ptrsize; // TODO: compute this
// ...
}
コメントにTODO: compute this
とあるように、これは一時的な措置であり、将来的には実行環境に依存しない形でこれらのサイズを動的に計算する必要があることが認識されていました。ハードコードされた値(例: 8
バイト)は、特定のアーキテクチャ(例: 64ビットシステム)では正しいかもしれませんが、32ビットシステムなど異なるアーキテクチャでは誤った情報となり、reflect
パッケージの動作に問題を引き起こす可能性がありました。
このコミットの目的は、このハードコードされた値を削除し、Go言語が提供するunsafe
パッケージのSizeof
関数を利用して、コンパイル時または実行時に正確なサイズを取得することにあります。これにより、reflect
パッケージの移植性と堅牢性が向上します。
前提知識の解説
Go言語のreflect
パッケージ
reflect
パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。これにより、変数の型、値、メソッドなどを動的に調べたり、操作したりすることができます。これは、例えばJSONエンコーダ/デコーダ、ORM(Object-Relational Mapping)、RPC(Remote Procedure Call)フレームワークなど、汎用的なライブラリを構築する際に非常に強力な機能となります。
reflect
パッケージが正しく機能するためには、Goの型がメモリ上でどのように表現されているか、特にそのサイズやアラインメントに関する正確な情報が必要です。
Go言語のunsafe
パッケージ
unsafe
パッケージは、Go言語の型安全性を意図的にバイパスする機能を提供します。その名の通り「安全ではない」操作を可能にするため、通常は使用を避けるべきですが、特定の低レベルな操作(例: C言語との相互運用、メモリレイアウトの直接操作、パフォーマンス最適化)において必要となる場合があります。
このパッケージが提供する主要な関数には以下のものがあります。
unsafe.Sizeof(x)
: 式x
が占めるメモリのバイト数を返します。これはコンパイル時に決定される定数です。unsafe.Alignof(x)
: 式x
が要求するアラインメントのバイト数を返します。unsafe.Offsetof(x.f)
: 構造体x
のフィールドf
のオフセット(構造体の先頭からのバイト数)を返します。unsafe.Pointer
: 任意の型のポインタとuintptr
(整数型)の間で変換できる特殊なポインタ型です。これにより、型システムを迂回してメモリを直接操作できます。
unsafe.Sizeof
は、特定の型のメモリサイズを正確に知る必要がある場合に非常に有用です。このコミットでは、ポインタとインターフェースの実際のサイズを、コンパイル時にunsafe.Sizeof
を使って決定することで、ハードコードの課題を解決しています。
ポインタとインターフェースのメモリ表現
- ポインタ: Goにおけるポインタは、メモリ上の特定のアドレスを指す変数です。そのサイズは、実行されているシステムのアーキテクチャ(32ビットか64ビットかなど)に依存します。32ビットシステムでは4バイト、64ビットシステムでは8バイトが一般的です。
- インターフェース: Goのインターフェースは、内部的に2つのワード(ポインタ)で構成されます。
- 型情報ポインタ (Type Pointer): インターフェースが保持している具体的な値の型情報(メソッドセットなど)を指すポインタ。
- データポインタ (Data Pointer): インターフェースが保持している具体的な値そのものを指すポインタ。 したがって、インターフェースのサイズは、ポインタのサイズの2倍になります。
技術的詳細
このコミットの核心は、reflect
パッケージが内部で使用するptrsize
(ポインタのサイズ)とinterfacesize
(インターフェースのサイズ)の計算方法を、静的なハードコードから動的なunsafe.Sizeof
の使用に切り替えた点にあります。
変更前は、init
関数内でptrsize = 8
とinterfacesize = 2*ptrsize
が設定されていました。これは64ビットシステムを前提とした値であり、32ビットシステムでは正しくありませんでした。
変更後、これらの変数はconst
として定義され、unsafe.Sizeof
を用いてコンパイル時にその値が決定されるようになりました。
var tmp_interface interface{} // used just to compute sizes of these constants
const (
ptrsize = unsafe.Sizeof(&tmp_interface);
interfacesize = unsafe.Sizeof(tmp_interface);
)
ここで重要なのは以下の点です。
tmp_interface interface{}
:unsafe.Sizeof
は変数や型のサイズを計算するために使用されます。ここでは、一時的なインターフェース型の変数tmp_interface
が宣言されています。この変数は実際に使用されることはなく、単にunsafe.Sizeof
の引数として、ポインタとインターフェースのサイズを計算するための「型情報」を提供するために存在します。ptrsize = unsafe.Sizeof(&tmp_interface)
:&tmp_interface
は、tmp_interface
変数のアドレス(ポインタ)を取得します。unsafe.Sizeof(&tmp_interface)
は、このポインタ自体のサイズを計算します。これにより、実行環境のポインタサイズ(例: 32ビットシステムなら4バイト、64ビットシステムなら8バイト)が正確にptrsize
に設定されます。
interfacesize = unsafe.Sizeof(tmp_interface)
:tmp_interface
はインターフェース型の変数です。unsafe.Sizeof(tmp_interface)
は、インターフェース型がメモリ上で占めるサイズを計算します。前述の通り、Goのインターフェースは型情報ポインタとデータポインタの2つのポインタで構成されるため、この値はptrsize
の2倍になります。
この変更により、reflect
パッケージは、コンパイルされるアーキテクチャに関わらず、常に正確なポインタとインターフェースのサイズ情報を取得できるようになりました。これはGo言語のクロスプラットフォーム対応において重要な改善です。
コアとなるコードの変更箇所
変更はsrc/lib/reflect/type.go
ファイルに集中しています。
--- a/src/lib/reflect/type.go
+++ b/src/lib/reflect/type.go
@@ -10,6 +10,7 @@ package reflect
import (
"utf8";
"sync";
+ "unsafe";
)
type Type interface
@@ -47,9 +48,11 @@ const (
UintptrKind;
)
-// Int is guaranteed large enough to store a size.
-var ptrsize int
-var interfacesize int
+var tmp_interface interface{}\t// used just to compute sizes of these constants
+const (
+\tptrsize = unsafe.Sizeof(&tmp_interface);
+\tinterfacesize = unsafe.Sizeof(tmp_interface);
+)
var missingString = "$missing$"\t// syntactic name for undefined type names
var dotDotDotString = "..."
@@ -401,9 +404,6 @@ func unlock() {
}
func init() {
-\tptrsize = 8;\t// TODO: compute this
-\tinterfacesize = 2*ptrsize;\t// TODO: compute this
-\
\tlock();\t// not necessary because of init ordering but be safe.
\ttypes = make(map[string] Type);\
コアとなるコードの解説
-
import "unsafe";
の追加:unsafe.Sizeof
関数を使用するために、unsafe
パッケージがインポートリストに追加されました。 -
ptrsize
とinterfacesize
の定義変更:- 変更前は、
ptrsize
とinterfacesize
はグローバル変数として宣言され、init
関数内で値が代入されていました。 - 変更後は、
tmp_interface
という一時的なインターフェース変数が導入され、ptrsize
とinterfacesize
はconst
(定数)として定義されました。これにより、これらの値はコンパイル時にunsafe.Sizeof
によって計算され、固定されます。 ptrsize = unsafe.Sizeof(&tmp_interface);
は、tmp_interface
へのポインタのサイズを計算し、システムのポインタサイズを正確に取得します。interfacesize = unsafe.Sizeof(tmp_interface);
は、インターフェース型自体のサイズを計算します。これは通常、ポインタサイズの2倍になります。
- 変更前は、
-
init
関数からのサイズ設定ロジックの削除:init
関数内にあったptrsize = 8;
とinterfacesize = 2*ptrsize;
の行が削除されました。これは、これらの値がすでにconst
としてコンパイル時に設定されるようになったため、不要になったためです。
この変更により、reflect
パッケージは、実行環境のアーキテクチャに依存しない、より正確で堅牢な型サイズ情報を提供するようになりました。
関連リンク
- Go言語の
reflect
パッケージに関する公式ドキュメント: https://pkg.go.dev/reflect - Go言語の
unsafe
パッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe - Go言語のインターフェースの内部構造に関する解説(例: "Go Data Structures: Interfaces" by Russ Cox): https://research.swtch.com/interfaces
参考にした情報源リンク
- Go言語の公式ドキュメント
- Go言語のソースコード(特に
src/lib/reflect/type.go
の変更履歴) - Go言語の
unsafe
パッケージに関する技術記事や解説 - Go言語のインターフェースのメモリ表現に関する技術記事や解説