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

[インデックス 15643] ファイルの概要

このコミットは、Go言語の公式仕様書(Go Programming Language Specification)における unsafe.Offsetof 関数の説明を明確化するものです。特に、構造体の埋め込みフィールド(embedded fields)とポインタの間接参照に関する Offsetof の挙動について、より正確な記述が追加されました。

コミット

commit 51338095eb6fa331b64716dea1fed4fa8f513fef
Author: Robert Griesemer <gri@golang.org>
Date:   Thu Mar 7 20:11:37 2013 -0800

    spec: clarify unsafe.Offsetof
    
    Fixes #4905.
    
    R=rsc, r, iant, ken
    CC=golang-dev
    https://golang.org/cl/7583043
---
 doc/go_spec.html | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/doc/go_spec.html b/doc/go_spec.html
index 0fc918471d..5268a5b16d 100644
--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
 	"Title": "The Go Programming Language Specification",
-	"Subtitle": "Version of March 4, 2013",
+\t"Subtitle": "Version of March 7, 2013",
 	"Path": "/ref/spec"
 }-->
 
@@ -5734,8 +5734,10 @@ as if <code>v</code> was declared via <code>var v = x</code>.
 </p>
 <p>
 The function <code>Offsetof</code> takes a (possibly parenthesized) <a href=\"#Selectors\">selector</a>
-denoting a struct field of any type and returns the field offset in bytes relative to the
-struct\'s address.\n+<code>s.f</code>, denoting a field <code>f</code> of the struct denoted by <code>s</code>\n+or <code>*s</code>, and returns the field offset in bytes relative to the struct\'s address.\n+If <code>f</code> is an <a href=\"#Struct_types\">embedded field</a>, it must be reachable\n+without pointer indirections through fields of the struct.\n For a struct <code>s</code> with field <code>f</code>:\n </p>\n \n```

## GitHub上でのコミットページへのリンク

[https://github.com/golang/go/commit/51338095eb6fa331b64716dea1fed4fa8f513fef](https://github.com/golang/go/commit/51338095eb6fa331b64716dea1fed4fa8f513fef)

## 元コミット内容

spec: clarify unsafe.Offsetof

Fixes #4905.

R=rsc, r, iant, ken CC=golang-dev https://golang.org/cl/7583043


## 変更の背景

この変更は、Go言語のIssue #4905「`unsafe.Offsetof` should not allow pointer indirections for embedded fields」を解決するために行われました。

Issue #4905では、`unsafe.Offsetof` 関数が、構造体の埋め込みフィールドに対してポインタの間接参照を介してアクセスされる場合に、予期せぬ挙動を示す可能性が指摘されていました。具体的には、`unsafe.Offsetof` は、構造体内のフィールドのオフセット(先頭からのバイト単位の距離)を計算するために使用されますが、埋め込みフィールドがポインタ型であり、そのポインタを介してさらにフィールドにアクセスするようなケースでは、`Offsetof` の定義が曖昧であったため、コンパイラの実装によって挙動が異なる可能性がありました。

Go言語の仕様は、言語の挙動を厳密に定義し、異なるコンパイラ実装間での一貫性を保証する上で非常に重要です。`unsafe` パッケージの関数は、Goの型安全性をバイパスして低レベルなメモリ操作を可能にするため、その挙動は特に明確である必要があります。この曖昧さを解消し、`unsafe.Offsetof` の正確なセマンティクスを保証するために、仕様書の記述が修正されました。

## 前提知識の解説

### `unsafe.Offsetof`

`unsafe` パッケージは、Go言語の型システムが提供する安全性を意図的にバイパスするための機能を提供します。これにより、低レベルなメモリ操作や、C言語との相互運用など、特定の高度なユースケースが可能になります。しかし、`unsafe` パッケージの使用は、プログラムの安全性や移植性を損なう可能性があるため、慎重に行う必要があります。

`unsafe.Offsetof` 関数は、構造体内の特定のフィールドが、その構造体の先頭から何バイト離れた位置にあるか(オフセット)を返します。この関数は、主にメモリレイアウトを理解したり、特定のメモリ位置に直接アクセスしたりする際に使用されます。

例:
```go
package main

import (
	"fmt"
	"unsafe"
)

type MyStruct struct {
	A int32
	B float64
	C bool
}

func main() {
	var s MyStruct
	fmt.Println("Offset of A:", unsafe.Offsetof(s.A)) // 0
	fmt.Println("Offset of B:", unsafe.Offsetof(s.B)) // 4 (on a 64-bit system, due to alignment)
	fmt.Println("Offset of C:", unsafe.Offsetof(s.C)) // 12 (on a 64-bit system, due to alignment)
}

構造体とフィールド

Go言語の構造体(struct)は、異なる型のフィールドをまとめるための複合データ型です。各フィールドは、構造体内で特定のオフセットを持ちます。

埋め込みフィールド (Embedded Fields)

Goの構造体は、他の構造体やインターフェースを「埋め込む」ことができます。これにより、埋め込まれた型のフィールドやメソッドが、外側の構造体のフィールドやメソッドであるかのように直接アクセスできるようになります。これは、継承に似た機能を提供しますが、Goでは「コンポジション(合成)」として扱われます。

例:

type Point struct {
	X, Y int
}

type Circle struct {
	Point // Embedded field
	Radius int
}

func main() {
	c := Circle{Point: Point{X: 10, Y: 20}, Radius: 5}
	fmt.Println(c.X) // Accessing embedded field's field directly
}

ポインタの間接参照 (Pointer Indirections)

ポインタは、メモリ上の特定のアドレスを指す変数です。ポインタの間接参照とは、ポインタが指すアドレスにある値にアクセスすることです。Goでは、* 演算子を使用してポインタの間接参照を行います。

例:

var x int = 10
var p *int = &x // pはxのアドレスを指す
fmt.Println(*p) // *pはxの値(10)にアクセスする

技術的詳細

このコミットの技術的な核心は、unsafe.Offsetof のセマンティクス、特に埋め込みフィールドとポインタの間接参照に関する曖昧さを解消することにあります。

変更前の仕様では、Offsetof は「構造体のフィールドを示すセレクタを取り、構造体のアドレスに対するフィールドのオフセットをバイト単位で返す」と記述されていました。しかし、この記述は、埋め込みフィールドがポインタ型である場合に、そのポインタを介してさらにフィールドにアクセスするようなケース(例: s.P.F のようなセレクタで、P がポインタ型の埋め込みフィールドである場合)の挙動を明確にしていませんでした。

Goの設計思想では、unsafe.Offsetof は、コンパイル時に決定できる静的なオフセットを計算することを意図しています。ポインタの間接参照を伴うフィールドアクセスは、実行時にポインタが指すアドレスが変化する可能性があるため、静的なオフセット計算には適しません。もし Offsetof がポインタの間接参照を許容してしまうと、その結果は予測不可能になったり、コンパイラの実装によって異なったりする可能性がありました。

このコミットでは、以下の重要な制約が追加されました。

If f is an embedded field, it must be reachable without pointer indirections through fields of the struct.

これは、「もし f が埋め込みフィールドである場合、それは構造体のフィールドを介したポインタの間接参照なしに到達可能でなければならない」という意味です。

この制約により、unsafe.Offsetof に渡されるセレクタは、常に直接的なフィールドアクセス(例: s.f)または、ポインタではない埋め込みフィールドを介したアクセス(例: s.EmbeddedStruct.f)に限定されることが明確になりました。ポインタ型の埋め込みフィールドを介して間接的にアクセスされるフィールドのオフセットは、unsafe.Offsetof の対象外となります。

この変更は、unsafe.Offsetof の使用をより安全で予測可能なものにし、Go言語の仕様の一貫性と厳密性を高めるものです。これにより、異なるGoコンパイラやツールが unsafe.Offsetof を同じように解釈し、同じ結果を生成することが保証されます。

コアとなるコードの変更箇所

変更は doc/go_spec.html ファイルに対して行われました。

--- a/doc/go_spec.html
+++ b/doc/go_spec.html
@@ -1,6 +1,6 @@
 <!--{
 	"Title": "The Go Programming Language Specification",
-	"Subtitle": "Version of March 4, 2013",
+\t"Subtitle": "Version of March 7, 2013",
 	"Path": "/ref/spec"
 }-->
 
@@ -5734,8 +5734,10 @@ as if <code>v</code> was declared via <code>var v = x</code>.
 </p>
 <p>
 The function <code>Offsetof</code> takes a (possibly parenthesized) <a href=\"#Selectors\">selector</a>
-denoting a struct field of any type and returns the field offset in bytes relative to the
-struct\'s address.\n+<code>s.f</code>, denoting a field <code>f</code> of the struct denoted by <code>s</code>\n+or <code>*s</code>, and returns the field offset in bytes relative to the struct\'s address.\n+If <code>f</code> is an <a href=\"#Struct_types\">embedded field</a>, it must be reachable\n+without pointer indirections through fields of the struct.\n For a struct <code>s</code> with field <code>f</code>:\n </p>\n \n```

## コアとなるコードの解説

変更された部分は、`unsafe.Offsetof` 関数の説明に関する段落です。

元の記述:
```html
The function <code>Offsetof</code> takes a (possibly parenthesized) <a href="#Selectors">selector</a>
denoting a struct field of any type and returns the field offset in bytes relative to the
struct's address.

この記述は、「Offsetof 関数は、任意の型の構造体フィールドを示すセレクタを取り、構造体のアドレスに対するフィールドのオフセットをバイト単位で返す」と述べていました。

新しい記述:

The function <code>Offsetof</code> takes a (possibly parenthesized) <a href="#Selectors">selector</a>
<code>s.f</code>, denoting a field <code>f</code> of the struct denoted by <code>s</code>
or <code>*s</code>, and returns the field offset in bytes relative to the struct's address.
If <code>f</code> is an <a href="#Struct_types">embedded field</a>, it must be reachable
without pointer indirections through fields of the struct.

追加された行は以下の通りです。

  1. <code>s.f</code>, denoting a field <code>f</code> of the struct denoted by <code>s</code> or <code>*s</code>,
    • これは、セレクタの一般的な形式が s.f であることを明示し、s が構造体自体であるか、構造体へのポインタであるか(*s)のどちらでも良いことを示しています。これは既存の挙動をより明確にしたものです。
  2. If <code>f</code> is an <a href="#Struct_types">embedded field</a>, it must be reachable without pointer indirections through fields of the struct.
    • これがこのコミットの最も重要な変更点です。埋め込みフィールド f のオフセットを計算する場合、そのフィールドが構造体内の他のフィールドを介したポインタの間接参照なしに到達可能でなければならないという制約が追加されました。これにより、unsafe.Offsetof が静的なオフセット計算に限定され、実行時のポインタ値に依存するような複雑なケースを排除することが明確になりました。

この修正により、unsafe.Offsetof の使用に関する曖昧さが解消され、Go言語の仕様がより堅牢になりました。

関連リンク

参考にした情報源リンク