[インデックス 14389] ファイルの概要
このコミットは、Goコンパイラのcmd/gc
パッケージにおけるclearslim
関数内のタイプミス(typo)を修正するものです。この修正は、ビルドの失敗を引き起こしていた問題を解決することを目的としています。
コミット
commit 0a47d2eff1d13e5867a76a472558b0d672879026
Author: Rémy Oudompheng <oudomphe@phare.normalesup.org>
Date: Tue Nov 13 07:08:29 2012 +0100
cmd/gc: fix typos in clearslim.
Fixes build failure.
R=golang-dev, bradfitz, dave
CC=golang-dev
https://golang.org/cl/6847043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0a47d2eff1d13e5867a76a472558b0d672879026
元コミット内容
cmd/gc: fix typos in clearslim.
Fixes build failure.
R=golang-dev, bradfitz, dave
CC=golang-dev
https://golang.org/cl/6847043
変更の背景
このコミットの背景には、Goコンパイラ(cmd/gc
)のビルドプロセスにおけるエラーがありました。具体的には、clearslim
関数内でメモリを割り当てる際に使用されるsizeof
演算子の引数が誤っていたため、コンパイル時に問題が発生していました。この誤りは「typo」(タイプミス)と表現されており、コードの論理的な誤りというよりも、記述上の不注意によるものとされています。結果として、このタイプミスがビルドの失敗を招いていたため、その修正が急務でした。
clearslim
関数は、Goのゼロ値(zero value)の概念に関連しています。Goでは、変数を宣言すると、その型に応じたゼロ値で自動的に初期化されます。例えば、数値型は0、ブール型はfalse
、文字列型は空文字列、ポインタやスライス、マップ、チャネルなどはnil
です。複合型(構造体や配列)の場合、その要素が再帰的にゼロ値で初期化されます。clearslim
のような関数は、このゼロ値初期化をコンパイラ内部で効率的に処理するために存在します。
前提知識の解説
このコミットを理解するためには、以下のGoコンパイラ内部の概念とC言語のsizeof
演算子に関する知識が必要です。
cmd/gc
: Go言語の公式コンパイラの一部であり、Goソースコードを機械語に変換する主要なコンポーネントです。Go 1.5以降はGo言語自体で書かれていますが、このコミットが作成された2012年当時はC言語で書かれていました。clearslim
関数: Goコンパイラのバックエンド(cmd/gc
)に存在する関数で、主に複合型(構造体や配列など)のフィールドをゼロ値で初期化する処理を担当します。特に、メモリを効率的にクリア(ゼロフィル)するために使用されます。Node
: Goコンパイラ内部で抽象構文木(AST: Abstract Syntax Tree)のノードを表す構造体です。ソースコードの各要素(変数、式、関数呼び出しなど)はNode
として表現されます。n->type->etype
:Node
構造体のn
が持つ型情報(type
フィールド)から、その型の基本要素型(etype
フィールド)を取得しています。etype
は、Goの組み込み型(TINT
、TFLOAT
、TCOMPLEX
など)を識別するための列挙型(enum)のようなものです。simtype
:etype
に対応する「単純化された型」を返す配列または関数です。コンパイラが型を処理する際に、より一般的なカテゴリに分類するために使用されます。TCOMPLEX64
,TCOMPLEX128
: Goのcomplex64
型(実部と虚部がfloat32
)およびcomplex128
型(実部と虚部がfloat64
)に対応する内部型定数です。TINT32
,TUINT32
,TUINT64
: Goのint32
、uint32
、uint64
型に対応する内部型定数です。mal
関数: Goコンパイラ内部で使用されるメモリ割り当て関数です。C言語のmalloc
に似ていますが、コンパイラ独自のメモリ管理メカニズムの一部です。引数としてバイト単位のサイズを受け取り、そのサイズのメモリブロックを割り当てます。sizeof
演算子 (C言語): C言語のsizeof
演算子は、オペランドの型または式のサイズをバイト単位で返します。sizeof(type)
: 指定された型のサイズを返します。sizeof(expression)
: 式の評価結果の型のサイズを返します。この際、式自体は評価されません。- ポインタと
sizeof
: ここが今回の問題の核心です。sizeof(pointer_variable)
: ポインタ変数自体のサイズを返します。これは通常、システムのアドレスバスの幅(例: 32ビットシステムでは4バイト、64ビットシステムでは8バイト)に依存します。ポインタが指すデータのサイズではありません。sizeof(*pointer_variable)
: ポインタが指す型のサイズを返します。つまり、ポインタを逆参照(dereference)した結果の型のサイズです。これが、ポインタが指す実際のデータに必要なメモリ量を正確に取得する方法です。
z.val.u.cval
:z
はコンパイラ内部の値を表すユニオン(union
)または構造体の一部であり、val
はその値、u
はそのユニオンのメンバー、cval
は複素数値を扱うためのポインタ型フィールドです。z.val.u.xval
: 同様に、xval
は整数値を扱うためのポインタ型フィールドです。mpmovecflt
/mpmovecfix
: Goコンパイラ内部の関数で、それぞれ浮動小数点数(complex float)および固定小数点数(fixed-point integer)の値を移動(コピー)または設定するために使用されます。ここでは、ゼロ値(0.0または0)を設定するために使われています。CTINT
: 内部で整数定数を表すための型定数です。
技術的詳細
このコミットの技術的な核心は、C言語のsizeof
演算子の誤用と、それがメモリ割り当てに与える影響です。
元のコードでは、mal
関数に渡す引数としてsizeof(z.val.u.cval)
とsizeof(z.val.u.xval)
が使用されていました。
z.val.u.cval
は、複素数値を指すポインタです(例:complex128 *
)。z.val.u.xval
は、多倍長整数値を指すポインタです(例:Mpint *
)。
したがって、sizeof(z.val.u.cval)
は「ポインタ変数z.val.u.cval
自体のサイズ」を返します。これは通常、32ビットシステムでは4バイト、64ビットシステムでは8バイトです。しかし、mal
関数が本当に必要としているのは、「z.val.u.cval
が指す複素数値のサイズ」です。例えば、complex128
型は16バイト(float64
が2つ)を必要とします。
同様に、sizeof(z.val.u.xval)
は「ポインタ変数z.val.u.xval
自体のサイズ」を返します。しかし、mal
関数が本当に必要としているのは、「z.val.u.xval
が指す多倍長整数値のサイズ」です。多倍長整数は可変長であるため、その構造体や表現に必要な実際のメモリサイズを割り当てる必要があります。
この誤りにより、mal
関数は必要なメモリ量よりもはるかに少ないメモリ(ポインタのサイズ分)しか割り当てていませんでした。その結果、割り当てられたメモリ領域が小さすぎたため、その後のmpmovecflt
やmpmovecfix
による値の書き込みが、割り当てられた領域を超えて行われ(バッファオーバーフロー)、メモリ破壊や未定義動作を引き起こしていました。これが、コンパイラのビルド失敗という形で現れたと考えられます。
修正後のコードでは、sizeof(*z.val.u.cval)
とsizeof(*z.val.u.xval)
が使用されています。
*z.val.u.cval
は、ポインタz.val.u.cval
が指す実際の複素数値です。*z.val.u.xval
は、ポインタz.val.u.xval
が指す実際の多倍長整数値です。
sizeof(*pointer_variable)
は、ポインタが指す型のサイズを正確に返します。これにより、mal
関数は複素数値や多倍長整数値を格納するために必要な正しいサイズのメモリを割り当てることができ、バッファオーバーフローを防ぎ、ビルドの失敗を解決しました。
この問題は、C言語におけるポインタとsizeof
演算子の基本的な理解の重要性を示す典型的な例です。特に、コンパイラのような低レベルのシステムプログラミングにおいては、このような細かなメモリ管理の誤りが深刻なバグにつながることがあります。
コアとなるコードの変更箇所
diff --git a/src/cmd/gc/gen.c b/src/cmd/gc/gen.c
index 456ca622fb..1cbda6245b 100644
--- a/src/cmd/gc/gen.c
+++ b/src/cmd/gc/gen.c
@@ -649,7 +649,7 @@ clearslim(Node *n)\n switch(simtype[n->type->etype]) {\n case TCOMPLEX64:\n case TCOMPLEX128:\n-\t\tz.val.u.cval = mal(sizeof(z.val.u.cval));\n+\t\tz.val.u.cval = mal(sizeof(*z.val.u.cval));\n \tmpmovecflt(&z.val.u.cval->real, 0.0);\n \tmpmovecflt(&z.val.u.cval->imag, 0.0);\n \tbreak;\n@@ -681,7 +681,7 @@ clearslim(Node *n)\n case TUINT32:\n case TUINT64:\n \tz.val.ctype = CTINT;\n-\t\tz.val.u.xval = mal(sizeof(z.val.u.xval));\n+\t\tz.val.u.xval = mal(sizeof(*z.val.u.xval));\n \tmpmovecfix(z.val.u.xval, 0);\n \tbreak;\
コアとなるコードの解説
変更はsrc/cmd/gc/gen.c
ファイル内のclearslim
関数にあります。この関数は、Goの型に応じてゼロ値を設定するためのメモリ割り当てと初期化を行っています。
-
複素数型 (
TCOMPLEX64
,TCOMPLEX128
) のケース:- 変更前:
z.val.u.cval = mal(sizeof(z.val.u.cval));
z.val.u.cval
は複素数値を指すポインタです。sizeof(z.val.u.cval)
は、このポインタ変数自体のサイズ(例: 8バイト)を返していました。- しかし、
mal
関数は、ポインタが指す実際の複素数値(complex128
なら16バイト)を格納するためのメモリを割り当てる必要がありました。
- 変更後:
z.val.u.cval = mal(sizeof(*z.val.u.cval));
*z.val.u.cval
は、ポインタz.val.u.cval
が指す実際の複素数値の型(例:struct { float64 real; float64 imag; }
)を表します。sizeof(*z.val.u.cval)
は、この複素数値の型が占める正しいサイズ(例: 16バイト)を返します。これにより、mal
は適切なサイズのメモリを割り当て、その後のmpmovecflt
による初期化が正しく行われるようになりました。
- 変更前:
-
符号なし整数型 (
TUINT32
,TUINT64
) のケース:- 変更前:
z.val.u.xval = mal(sizeof(z.val.u.xval));
z.val.u.xval
は多倍長整数値を指すポインタです。sizeof(z.val.u.xval)
は、このポインタ変数自体のサイズ(例: 8バイト)を返していました。- しかし、
mal
関数は、ポインタが指す実際の多倍長整数値の構造体を格納するためのメモリを割り当てる必要がありました。
- 変更後:
z.val.u.xval = mal(sizeof(*z.val.u.xval));
*z.val.u.xval
は、ポインタz.val.u.xval
が指す実際の多倍長整数値の型(例:Mpint
構造体)を表します。sizeof(*z.val.u.xval)
は、この多倍長整数値の型が占める正しいサイズを返します。これにより、mal
は適切なサイズのメモリを割り当て、その後のmpmovecfix
による初期化が正しく行われるようになりました。
- 変更前:
この修正は、C言語におけるポインタのサイズと、ポインタが指すオブジェクトのサイズの区別という、基本的ながらも非常に重要な概念の適用例です。この「typo」が、コンパイラのビルド失敗という形で表面化していたバグの原因でした。
関連リンク
- Go Gerrit Code Review: https://golang.org/cl/6847043
参考にした情報源リンク
- C言語
sizeof
演算子: https://ja.cppreference.com/w/c/language/sizeof - Go言語のゼロ値: https://go.dev/tour/basics/12
- Goコンパイラの歴史 (Go 1.5以降のセルフホスト化): https://go.dev/blog/go1.5release
- Goコンパイラ内部構造に関する一般的な情報 (古い情報も含む):
- https://go.dev/doc/articles/go_compiler.html (これはGo 1.0に関する古い記事ですが、当時の
cmd/gc
の役割を理解するのに役立ちます) - https://go.dev/src/cmd/gc/ (Goコンパイラのソースコード)
- https://go.dev/doc/articles/go_compiler.html (これはGo 1.0に関する古い記事ですが、当時の
- Goの多倍長整数パッケージ
math/big
: https://pkg.go.dev/math/big (コンパイラ内部のMpint
と直接関係はありませんが、Goが多倍長整数をどのように扱うかの概念的な背景を提供します) - Goの複素数型: https://go.dev/ref/spec#Complex_numbers
- C言語のポインタとメモリ割り当てに関する一般的な情報源 (例:
malloc
): https://ja.cppreference.com/w/c/memory/malloc - バッファオーバーフロー: https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%95%E3%83%AD%E3%83%BC