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

[インデックス 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内でptrsizeinterfacesizeが以下のようにハードコードされていました。

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つのワード(ポインタ)で構成されます。
    1. 型情報ポインタ (Type Pointer): インターフェースが保持している具体的な値の型情報(メソッドセットなど)を指すポインタ。
    2. データポインタ (Data Pointer): インターフェースが保持している具体的な値そのものを指すポインタ。 したがって、インターフェースのサイズは、ポインタのサイズの2倍になります。

技術的詳細

このコミットの核心は、reflectパッケージが内部で使用するptrsize(ポインタのサイズ)とinterfacesize(インターフェースのサイズ)の計算方法を、静的なハードコードから動的なunsafe.Sizeofの使用に切り替えた点にあります。

変更前は、init関数内でptrsize = 8interfacesize = 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);
)

ここで重要なのは以下の点です。

  1. tmp_interface interface{}: unsafe.Sizeofは変数や型のサイズを計算するために使用されます。ここでは、一時的なインターフェース型の変数tmp_interfaceが宣言されています。この変数は実際に使用されることはなく、単にunsafe.Sizeofの引数として、ポインタとインターフェースのサイズを計算するための「型情報」を提供するために存在します。
  2. ptrsize = unsafe.Sizeof(&tmp_interface):
    • &tmp_interfaceは、tmp_interface変数のアドレス(ポインタ)を取得します。
    • unsafe.Sizeof(&tmp_interface)は、このポインタ自体のサイズを計算します。これにより、実行環境のポインタサイズ(例: 32ビットシステムなら4バイト、64ビットシステムなら8バイト)が正確にptrsizeに設定されます。
  3. 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);\

コアとなるコードの解説

  1. import "unsafe"; の追加: unsafe.Sizeof関数を使用するために、unsafeパッケージがインポートリストに追加されました。

  2. ptrsizeinterfacesizeの定義変更:

    • 変更前は、ptrsizeinterfacesizeはグローバル変数として宣言され、init関数内で値が代入されていました。
    • 変更後は、tmp_interfaceという一時的なインターフェース変数が導入され、ptrsizeinterfacesizeconst(定数)として定義されました。これにより、これらの値はコンパイル時にunsafe.Sizeofによって計算され、固定されます。
    • ptrsize = unsafe.Sizeof(&tmp_interface); は、tmp_interfaceへのポインタのサイズを計算し、システムのポインタサイズを正確に取得します。
    • interfacesize = unsafe.Sizeof(tmp_interface); は、インターフェース型自体のサイズを計算します。これは通常、ポインタサイズの2倍になります。
  3. init関数からのサイズ設定ロジックの削除: init関数内にあったptrsize = 8;interfacesize = 2*ptrsize;の行が削除されました。これは、これらの値がすでにconstとしてコンパイル時に設定されるようになったため、不要になったためです。

この変更により、reflectパッケージは、実行環境のアーキテクチャに依存しない、より正確で堅牢な型サイズ情報を提供するようになりました。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • Go言語のソースコード(特にsrc/lib/reflect/type.goの変更履歴)
  • Go言語のunsafeパッケージに関する技術記事や解説
  • Go言語のインターフェースのメモリ表現に関する技術記事や解説