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

[インデックス 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」を解消し、より正確なサイズ取得方法を導入しています。

  1. 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バイト(型情報ポインタ + データポインタ)を占めます。

  2. 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およびunsafeパッケージ)
  • Go言語のブログ記事(特にinterface{}stringの内部表現に関するもの)
  • Go言語のソースコード(src/runtime/src/reflect/ディレクトリ)
  • Go言語のコミット履歴(GitHub)
  • Go言語に関する技術記事やフォーラムの議論