[インデックス 1694] ファイルの概要
このコミットは、Go言語のreflect
パッケージにおける型情報の初期化に関するものです。具体的には、unsafe.Sizeof
関数を用いて、特定の組み込み型のメモリサイズをより正確に、かつ動的に取得するように変更しています。これにより、Goの型システムが内部的に扱う型のサイズ定義が、より堅牢でポータブルなものになります。
コミット
commit d0424faf176fc4ebd6fe5104817a0839cd740e3e
Author: Russ Cox <rsc@golang.org>
Date: Tue Feb 17 13:40:28 2009 -0800
few more Sizeof.
R=r
DELTA=3 (0 added, 1 deleted, 2 changed)
OCL=25106
CL=25106
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/d0424faf176fc4ebd6fe5104817a0839cd740e3e
元コミット内容
few more Sizeof.
R=r
DELTA=3 (0 added, 1 deleted, 2 changed)
OCL=25106
CL=25106
変更の背景
このコミットが行われた2009年2月は、Go言語がまだ一般に公開される前の開発初期段階にあたります。Go言語の設計目標の一つに、効率的なメモリ利用と高速な実行があります。reflect
パッケージは、実行時に型情報を扱うための重要な機能を提供しますが、その内部では各型のメモリサイズを正確に把握する必要があります。
初期のGo言語では、一部の型のサイズがハードコードされていたり、コメントで「TODO」として残されていたりする箇所がありました。例えば、interface{}
(空インターフェース)やstring
型のような、内部表現がポインタや長さ情報を含む複合的な構造を持つ型の場合、そのサイズはアーキテクチャやコンパイラの実装によって変動する可能性があります。
このコミットの背景には、これらの型のサイズ定義をより正確にし、将来的なアーキテクチャ変更やコンパイラの進化にも対応できるように、unsafe.Sizeof
関数を利用して動的にサイズを取得するという意図があります。これにより、reflect
パッケージが提供する型情報が、より信頼性の高いものとなります。
前提知識の解説
Go言語のreflect
パッケージ
reflect
パッケージは、Goプログラムが実行時に自身の構造を検査(introspection)し、操作(manipulation)するための機能を提供します。これにより、変数の型、値、メソッドなどを実行時に調べたり、動的に関数を呼び出したり、構造体のフィールドにアクセスしたりすることが可能になります。
reflect
パッケージの主要な概念には以下があります。
Type
: Goの型そのものを表すインターフェース。reflect.TypeOf(x)
で取得できます。Value
: Goの値そのものを表す構造体。reflect.ValueOf(x)
で取得できます。Kind
: 型の基本的なカテゴリ(例:Int
,String
,Struct
,Slice
,Interface
など)を表す定数。
reflect
パッケージは、ジェネリックプログラミングの代替手段として、あるいはシリアライゼーション/デシリアライゼーション、ORM、RPCフレームワークなどの実装において広く利用されます。
unsafe
パッケージとunsafe.Sizeof
unsafe
パッケージは、Go言語の型安全性を意図的にバイパスする機能を提供します。その名の通り「安全ではない」操作を可能にするため、通常は使用を避けるべきですが、特定の高性能なコードやシステムプログラミング、あるいはGoランタイムの内部実装など、限られた状況で必要とされます。
unsafe.Sizeof
関数は、Goの組み込み関数の一つで、引数として与えられた式の型のメモリ上でのサイズ(バイト単位)を返します。このサイズは、その型の値がメモリ上で占めるバイト数であり、パディングを含む場合があります。unsafe.Sizeof
はコンパイル時に評価される定数式であり、実行時のオーバーヘッドはありません。
Go言語におけるinterface{}
とstring
の内部表現
-
interface{}
(空インターフェース): Goのインターフェースは、内部的には2つのワード(ポインタ)で構成されます。1つは型情報(_type
ポインタ)を指し、もう1つはそのインターフェースが保持する値(データポインタ)を指します。したがって、64ビットシステムでは通常16バイト(8バイト + 8バイト)のサイズを持ちます。このコミットでは、unsafe.Sizeof(true.(interface{}))
という式を使って、bool
型の値を空インターフェースに変換した際のインターフェース自体のサイズを取得しています。これは、インターフェースのサイズが固定であることを確認するための一例です。 -
string
: Goの文字列は、内部的には読み取り専用のバイトスライスであり、ポインタと長さの2つの要素で構成されます。ポインタは文字列のデータが格納されているメモリ上のアドレスを指し、長さはその文字列のバイト数を表します。64ビットシステムでは通常16バイト(8バイト + 8バイト)のサイズを持ちます。このコミットでは、unsafe.Sizeof("")
という式を使って、空文字列のstring
型自体のサイズを取得しています。これは、文字列のデータ内容に関わらず、string
型変数が占めるメモリサイズが固定であることを確認するためです。
技術的詳細
このコミットは、src/lib/reflect/type.go
ファイル内のnewBasicType
関数によって初期化される組み込み型のサイズ定義を修正しています。
変更前は、DotDotDot
(可変引数...
の内部表現)のサイズが16
とハードコードされており、コメントで「TODO(r): size of interface?」と書かれていました。また、String
型のサイズもunsafe.Sizeof(string(0))
となっており、コメントで「TODO(rsc): Sizeof("") should work, doesn't.」と書かれていました。
このコミットでは、これらの「TODO」を解消し、より正確なサイズ取得方法を導入しています。
-
DotDotDot
のサイズ: 変更前:DotDotDot = newBasicType(dotDotDotString, DotDotDotKind, 16); // TODO(r): size of interface?
変更後:DotDotDot = newBasicType(dotDotDotString, DotDotDotKind, unsafe.Sizeof(true.(interface{})));
DotDotDotKind
は可変引数(...
)の型を表す内部的なKind
です。Goの可変引数は、内部的にはスライスとして扱われるか、あるいはインターフェースのメカニズムと関連しています。この変更では、true.(interface{})
という型アサーションを用いて、bool
型の値を空インターフェースに変換した際のインターフェース自体のサイズをunsafe.Sizeof
で取得しています。これは、インターフェースのサイズがプラットフォームに依存する可能性があり、それを動的に取得することで、よりポータブルなコードにする意図があります。64ビットシステムでは、インターフェースは通常16バイト(型情報ポインタ + データポインタ)を占めます。 -
String
のサイズ: 変更前:String = newBasicType("string", StringKind, unsafe.Sizeof(string(0)));
変更後:String = newBasicType("string", StringKind, unsafe.Sizeof(""));
以前はunsafe.Sizeof(string(0))
という、やや不自然な方法で文字列のサイズを取得しようとしていました。string(0)
はASCIIコード0の文字を含む文字列を生成しようとしますが、これは文字列の内部表現のサイズを正確に反映しない可能性がありました。この変更では、unsafe.Sizeof("")
、つまり空文字列リテラルをunsafe.Sizeof
に渡すことで、string
型変数がメモリ上で占める固定のサイズ(ポインタと長さの2つのワード、通常16バイト)を正確に取得するように修正しています。これは、文字列の内容に関わらず、文字列型変数のサイズは一定であるというGoの仕様に基づいています。
これらの変更は、Goランタイムの初期化フェーズにおいて、reflect
パッケージが扱う組み込み型のメモリサイズを、ハードコードされた値ではなく、unsafe.Sizeof
を用いた動的な評価によって決定するように改善したものです。これにより、Go言語の基盤がより堅牢になり、異なるアーキテクチャやコンパイラ環境への適応性が向上します。
コアとなるコードの変更箇所
src/lib/reflect/type.go
--- a/src/lib/reflect/type.go
+++ b/src/lib/reflect/type.go
@@ -107,7 +107,7 @@ func newBasicType(name string, kind int, size int) Type {
// Prebuilt basic types
var (
Missing = newBasicType(missingString, MissingKind, 1);
- DotDotDot = newBasicType(dotDotDotString, DotDotDotKind, 16); // TODO(r): size of interface?
+ DotDotDot = newBasicType(dotDotDotString, DotDotDotKind, unsafe.Sizeof(true.(interface{})));
Bool = newBasicType("bool", BoolKind, unsafe.Sizeof(true));
Int = newBasicType("int", IntKind, unsafe.Sizeof(int(0)));
Int8 = newBasicType("int8", Int8Kind, 1);
@@ -124,8 +124,7 @@ var (
Float32 = newBasicType("float32", Float32Kind, 4);
Float64 = newBasicType("float64", Float64Kind, 8);
Float80 = newBasicType("float80", Float80Kind, 10); // TODO: strange size?
- // TODO(rsc): Sizeof("") should work, doesn't.
- String = newBasicType("string", StringKind, unsafe.Sizeof(string(0)));
+ String = newBasicType("string", StringKind, unsafe.Sizeof(""));
)
// Stub types allow us to defer evaluating type names until needed.
コアとなるコードの解説
上記の差分は、src/lib/reflect/type.go
ファイル内のvar
ブロックで定義されている、Goの組み込み型(Missing
, DotDotDot
, Bool
, Int
, Int8
, ..., String
など)の初期化部分を示しています。
newBasicType
関数は、型名、Kind
(型のカテゴリ)、そしてその型のメモリサイズを受け取って、Type
インターフェースを実装するオブジェクトを生成します。このコミットの目的は、このnewBasicType
に渡すsize
引数をより正確にすることです。
-
DotDotDot
の変更:DotDotDot = newBasicType(dotDotDotString, DotDotDotKind, unsafe.Sizeof(true.(interface{})));
DotDotDotKind
はGoの可変引数(...
)の内部表現に関連する型です。以前はサイズが16
と固定されていましたが、この変更によりunsafe.Sizeof(true.(interface{}))
が使用されています。true.(interface{})
はbool
型の値true
を空インターフェースinterface{}
に型アサーションする式です。Goのインターフェースは、その型情報と値のポインタを保持するため、通常は2つのワード(64ビットシステムでは16バイト)を占めます。この変更は、インターフェースの実際のサイズをプラットフォームに依存しない形で取得し、DotDotDotKind
のサイズとして設定することを意図しています。 -
String
の変更:String = newBasicType("string", StringKind, unsafe.Sizeof(""));
以前はunsafe.Sizeof(string(0))
という式で文字列のサイズを取得しようとしていましたが、これは文字列の内部表現のサイズを正確に反映しない可能性がありました。Goの文字列は、データへのポインタと長さの2つの要素で構成される固定サイズの構造体です。この変更では、unsafe.Sizeof("")
、つまり空文字列リテラルをunsafe.Sizeof
に渡すことで、string
型変数がメモリ上で占める固定のサイズ(通常16バイト)を正確に取得するように修正しています。これにより、文字列の内容に関わらず、string
型変数のサイズが一定であるというGoの仕様が正しく反映されます。
これらの変更は、Goのreflect
パッケージが提供する型情報が、より正確で、異なるアーキテクチャやコンパイラ環境においても一貫性を持つようにするための、Goランタイムの初期段階における重要な改善です。
関連リンク
- Go言語の
reflect
パッケージに関する公式ドキュメント: https://pkg.go.dev/reflect - Go言語の
unsafe
パッケージに関する公式ドキュメント: https://pkg.go.dev/unsafe - Go言語のインターフェースの内部表現に関する解説(例: The Laws of Reflection - Rob Pike): https://go.dev/blog/laws-of-reflection
- Go言語の文字列の内部表現に関する解説(例: Go Slices: usage and internals - The Go Blog): https://go.dev/blog/go-slices-usage-and-internals
参考にした情報源リンク
- Go言語の公式ドキュメント(
reflect
およびunsafe
パッケージ) - Go言語のブログ記事(特に
interface{}
やstring
の内部表現に関するもの) - Go言語のソースコード(
src/runtime/
やsrc/reflect/
ディレクトリ) - Go言語のコミット履歴(GitHub)
- Go言語に関する技術記事やフォーラムの議論