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

[インデックス 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.Pointeruintptr の関係: unsafe.Pointer はポインタであり、ガベージコレクタによって追跡されますが、uintptr は単なる整数であり、追跡されません。この違いは、ガベージコレクションの挙動に影響を与えます。unsafe.Pointeruintptr に変換し、その uintptr を保持し続けると、元のポインタが指していたメモリがガベージコレクタによって解放されてしまう可能性があります。

4. Goにおける型変換

Goでは、異なる型間で値を変換する際に、明示的な型変換(キャスト)が必要です。ポインタ型と unsafe.Pointer、そして uintptr の間には、特定の変換ルールが存在します。

  • 任意の型のポインタ ↔ unsafe.Pointer: 任意の型のポインタ(例: *int, *string)は unsafe.Pointer に変換でき、その逆も可能です。
  • unsafe.Pointeruintptr: unsafe.Pointeruintptr に変換でき、その逆も可能です。

これらの変換は、低レベルなメモリ操作や、Goの型システムでは表現できないような操作を行うために利用されます。

技術的詳細

このコミットの技術的な核心は、Go言語の仕様書における unsafe.Pointer の変換規則の明確化です。具体的には、以下の点が変更されました。

  1. 変換対象の明確化: 以前の記述では、「任意のポインタまたは uintptr の基底型を持つ値は Pointer に変換できる」とされていました。この「Pointer」が unsafe.Pointer 型そのものを指すのか、あるいは unsafe.Pointer を基底型とするカスタム型(例: type ptr unsafe.Pointer)も含むのかが不明瞭でした。今回の変更により、「任意のポインタまたは uintptr の基底型を持つ値は Pointer 型に変換できる」と修正され、「Pointer 型」という表現が使われることで、unsafe.Pointer を基底型とするカスタム型も変換の対象となることが明確になりました。

  2. 具体例の追加: 曖昧さを解消するために、具体的なコード例が追加されました。

    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(&amp;f))
+
+type ptr unsafe.Pointer
+bits = *(*uint64)(ptr(&amp;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 パッケージに関するセクションで行われました。

  1. 変更前:

    <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 を基底型とするカスタム型も含むのかが不明瞭でした。

  2. 変更後:

    <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(&amp;f))
    +
    +type ptr unsafe.Pointer
    +bits = *(*uint64)(ptr(&amp;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.Pointerbits = *(*uint64)(ptr(&f)) は、unsafe.Pointer を基底型とするカスタム型 ptr を定義し、そのカスタム型を介して変換が行えることを示しています。この例が追加されたことで、仕様の曖昧さが解消され、unsafe.Pointer を基底型とする型も変換可能であることが明確に示されました。

この変更は、Go言語の仕様書が、実際のコンパイラの挙動と reflect パッケージのような重要な標準ライブラリの内部実装に即して更新されたことを意味します。これにより、Go言語の低レベルなメモリ操作に関する理解が深まり、開発者がより正確な知識に基づいてコードを書けるようになりました。

関連リンク

参考にした情報源リンク