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

[インデックス 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の組み込み型(TINTTFLOATTCOMPLEXなど)を識別するための列挙型(enum)のようなものです。
  • simtype: etypeに対応する「単純化された型」を返す配列または関数です。コンパイラが型を処理する際に、より一般的なカテゴリに分類するために使用されます。
  • TCOMPLEX64, TCOMPLEX128: Goのcomplex64型(実部と虚部がfloat32)およびcomplex128型(実部と虚部がfloat64)に対応する内部型定数です。
  • TINT32, TUINT32, TUINT64: Goのint32uint32uint64型に対応する内部型定数です。
  • 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関数は必要なメモリ量よりもはるかに少ないメモリ(ポインタのサイズ分)しか割り当てていませんでした。その結果、割り当てられたメモリ領域が小さすぎたため、その後のmpmovecfltmpmovecfixによる値の書き込みが、割り当てられた領域を超えて行われ(バッファオーバーフロー)、メモリ破壊や未定義動作を引き起こしていました。これが、コンパイラのビルド失敗という形で現れたと考えられます。

修正後のコードでは、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の型に応じてゼロ値を設定するためのメモリ割り当てと初期化を行っています。

  1. 複素数型 (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による初期化が正しく行われるようになりました。
  2. 符号なし整数型 (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」が、コンパイラのビルド失敗という形で表面化していたバグの原因でした。

関連リンク

参考にした情報源リンク