[インデックス 15188] ファイルの概要
このコミットは、Go言語の公式仕様書である doc/go_spec.html
ファイルに対する変更です。このファイルは、Go言語の構文、セマンティクス、組み込み関数、パッケージなど、言語のあらゆる側面を定義する重要なドキュメントです。この特定の変更は、unsafe.Pointer
型の変換に関する記述を明確にすることを目的としています。
コミット
- コミットハッシュ:
81eb930f7e021e334ec2b54dc4ba6b1ab825887f
- Author: Russ Cox rsc@golang.org
- Date: Sat Feb 9 17:36:31 2013 -0500
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/81eb930f7e021e334ec2b54dc4ba6b1ab825887f
元コミット内容
spec: clarify that any unsafe.Pointer type is okay in conversion
The spec is not clear about whether this is allowed or not,
but both compilers allow it, because the reflect implementation
takes advantage of it. Document current behavior.
Fixes #4679.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/7303064
変更の背景
このコミットの主な背景は、Go言語の仕様書における unsafe.Pointer
型の変換に関する記述の曖昧さでした。既存のGoコンパイラ(gcとgccgoの両方)は、unsafe.Pointer
型が他のポインタ型や uintptr
型との間で変換される際に、その unsafe.Pointer
が直接 unsafe.Pointer
型であるか、あるいは unsafe.Pointer
を基底型とするカスタム型であるかを区別せずに許可していました。しかし、当時の仕様書ではこの挙動が明確に記述されていませんでした。
特に、Goの reflect
パッケージの実装がこの「unsafe.Pointer
を基底型とするカスタム型も変換可能である」という挙動に依存していたため、実際のコンパイラの挙動と仕様書の記述との間に乖離が生じていました。この乖離は、開発者がGo言語のポインタ操作、特に低レベルなメモリ操作を行う際に混乱を招く可能性がありました。
このコミットは、この曖昧さを解消し、コンパイラが既に実装している挙動(reflect
パッケージが依存している挙動)を仕様書に明記することで、言語の整合性と開発者の理解を深めることを目的としています。Fixes #4679
とあるように、これは特定の課題(issue)を解決するための変更でした。
前提知識の解説
このコミットの変更内容を理解するためには、以下のGo言語の概念を理解しておく必要があります。
1. Go言語の型システム
Go言語は静的型付け言語であり、変数は特定の型を持ちます。型はデータの種類と、そのデータに対して実行できる操作を定義します。Goの型システムは比較的シンプルですが、ポインタやインターフェースなど、低レベルな操作を可能にする機能も備えています。
2. unsafe
パッケージと unsafe.Pointer
unsafe
パッケージは、Go言語の型安全性を意図的にバイパスするための機能を提供します。その中でも unsafe.Pointer
は非常に重要です。
unsafe.Pointer
: 任意の型のポインタを保持できる特別なポインタ型です。これはC言語のvoid*
に似ていますが、Goの型システムとは異なるルールで扱われます。unsafe.Pointer
を使用することで、異なる型のポインタ間で変換を行ったり、ポインタとuintptr
の間で変換を行ったりすることが可能になります。- 型安全性からの逸脱:
unsafe.Pointer
を使用すると、Goの型安全性の保証が失われます。誤った使い方をすると、メモリ破壊、クラッシュ、未定義の動作など、深刻な問題を引き起こす可能性があります。そのため、unsafe
パッケージの使用は、パフォーマンスが極めて重要である場合や、特定のシステムプログラミングタスク(例:reflect
パッケージのような言語ランタイムの内部実装)に限定されるべきです。
3. uintptr
型
uintptr
: 符号なし整数型であり、ポインタのビットパターンを保持するのに十分な大きさがあります。uintptr
は整数であり、ポインタではありません。つまり、ガベージコレクタはuintptr
が指すメモリを追跡しません。unsafe.Pointer
とuintptr
の関係:unsafe.Pointer
はポインタであり、ガベージコレクタによって追跡されますが、uintptr
は単なる整数であり、追跡されません。この違いは、ガベージコレクションの挙動に影響を与えます。unsafe.Pointer
をuintptr
に変換し、そのuintptr
を保持し続けると、元のポインタが指していたメモリがガベージコレクタによって解放されてしまう可能性があります。
4. Goにおける型変換
Goでは、異なる型間で値を変換する際に、明示的な型変換(キャスト)が必要です。ポインタ型と unsafe.Pointer
、そして uintptr
の間には、特定の変換ルールが存在します。
- 任意の型のポインタ ↔
unsafe.Pointer
: 任意の型のポインタ(例:*int
,*string
)はunsafe.Pointer
に変換でき、その逆も可能です。 unsafe.Pointer
↔uintptr
:unsafe.Pointer
はuintptr
に変換でき、その逆も可能です。
これらの変換は、低レベルなメモリ操作や、Goの型システムでは表現できないような操作を行うために利用されます。
技術的詳細
このコミットの技術的な核心は、Go言語の仕様書における unsafe.Pointer
の変換規則の明確化です。具体的には、以下の点が変更されました。
-
変換対象の明確化: 以前の記述では、「任意のポインタまたは
uintptr
の基底型を持つ値はPointer
に変換できる」とされていました。この「Pointer
」がunsafe.Pointer
型そのものを指すのか、あるいはunsafe.Pointer
を基底型とするカスタム型(例:type ptr unsafe.Pointer
)も含むのかが不明瞭でした。今回の変更により、「任意のポインタまたはuintptr
の基底型を持つ値はPointer
型に変換できる」と修正され、「Pointer
型」という表現が使われることで、unsafe.Pointer
を基底型とするカスタム型も変換の対象となることが明確になりました。 -
具体例の追加: 曖昧さを解消するために、具体的なコード例が追加されました。
var f float64 bits = *(*uint64)(unsafe.Pointer(&f)) type ptr unsafe.Pointer bits = *(*uint64)(ptr(&f))
この例は、
float64
型の変数のアドレスをunsafe.Pointer
に変換し、さらにそれを*uint64
に変換して、float64
のビット表現をuint64
として読み取る操作を示しています。 特に重要なのは、2番目の例です。type ptr unsafe.Pointer
と定義されたカスタム型ptr
を介して&f
を変換している点です。これにより、unsafe.Pointer
を基底型とするカスタム型も、unsafe.Pointer
と同様に変換可能であることが視覚的に示されました。
この変更は、Goコンパイラが既にこの挙動を許可しており、特に reflect
パッケージの内部実装がこの柔軟な変換に依存しているという事実に基づいています。reflect
パッケージは、Goの型情報を実行時に検査・操作するための強力な機能を提供しますが、その実装には低レベルなメモリ操作が不可欠であり、unsafe.Pointer
の柔軟な変換能力が利用されています。
したがって、このコミットは、既存のコンパイラの挙動と reflect
パッケージの要件を仕様書に反映させることで、Go言語の仕様の正確性と一貫性を向上させました。これは、Go言語の「実用主義」の原則、すなわち「実際に動いているものを正とする」という考え方の一例とも言えます。
コアとなるコードの変更箇所
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -5610,9 +5610,18 @@ func Sizeof(variable ArbitraryType) uintptr
</pre>
<p>
-Any pointer or value of <a href="#Types">underlying type</a> <code>uintptr</code> can be converted into
-a <code>Pointer</code> and vice versa.
+Any pointer or value of <a href="#Types">underlying type</a> <code>uintptr</code> can be converted to
+a <code>Pointer</code> type and vice versa.
</p>
+
+<pre>
+var f float64
+bits = *(*uint64)(unsafe.Pointer(&f))
+
+type ptr unsafe.Pointer
+bits = *(*uint64)(ptr(&f))
+</pre>
+
<p>
The functions <code>Alignof</code> and <code>Sizeof</code> take an expression <code>x</code>
of any type and return the alignment or size, respectively, of a hypothetical variable <code>v</code>
コアとなるコードの解説
変更は doc/go_spec.html
ファイルの unsafe
パッケージに関するセクションで行われました。
-
変更前:
<p> Any pointer or value of <a href="#Types">underlying type</a> <code>uintptr</code> can be converted into a <code>Pointer</code> and vice versa. </p>
この記述では、「
Pointer
」がunsafe.Pointer
型そのものを指すのか、あるいはunsafe.Pointer
を基底型とするカスタム型も含むのかが不明瞭でした。 -
変更後:
<p> Any pointer or value of <a href="#Types">underlying type</a> <code>uintptr</code> can be converted to a <code>Pointer</code> type and vice versa. </p> + +<pre> +var f float64 +bits = *(*uint64)(unsafe.Pointer(&f)) + +type ptr unsafe.Pointer +bits = *(*uint64)(ptr(&f)) +</pre>
- 文言の修正: 「
a Pointer
」が「a Pointer type
」に変更されました。これにより、unsafe.Pointer
型そのものだけでなく、unsafe.Pointer
を基底型とする任意の型(例:type MyUnsafePointer unsafe.Pointer
)も変換の対象となることが明確に示されました。 - コード例の追加: 変更後の段落の直後に、具体的なGoコードの例が追加されました。
bits = *(*uint64)(unsafe.Pointer(&f))
は、float64
のアドレスを直接unsafe.Pointer
に変換し、さらに*uint64
に変換してビットを読み取る一般的なパターンを示しています。type ptr unsafe.Pointer
とbits = *(*uint64)(ptr(&f))
は、unsafe.Pointer
を基底型とするカスタム型ptr
を定義し、そのカスタム型を介して変換が行えることを示しています。この例が追加されたことで、仕様の曖昧さが解消され、unsafe.Pointer
を基底型とする型も変換可能であることが明確に示されました。
- 文言の修正: 「
この変更は、Go言語の仕様書が、実際のコンパイラの挙動と reflect
パッケージのような重要な標準ライブラリの内部実装に即して更新されたことを意味します。これにより、Go言語の低レベルなメモリ操作に関する理解が深まり、開発者がより正確な知識に基づいてコードを書けるようになりました。
関連リンク
- Go言語の変更リスト (CL): https://golang.org/cl/7303064
参考にした情報源リンク
- Go言語の公式仕様書: https://go.dev/ref/spec (このコミットが変更したドキュメントの最新版)
- Go言語の
unsafe
パッケージのドキュメント: https://pkg.go.dev/unsafe - Go言語の
reflect
パッケージのドキュメント: https://pkg.go.dev/reflect - Go言語のissueトラッカー (Go issue #4679 は直接見つかりませんでしたが、古いissueはアーカイブされている可能性があります): https://github.com/golang/go/issues