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

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

このコミットは、Go言語のディストリビューションツール (cmd/dist) におけるWindows環境でのメモリ再割り当て処理に関する変更です。具体的には、HeapReAlloc 関数の呼び出しから HEAP_GENERATE_EXCEPTIONS フラグを削除しています。

コミット

commit eaf640dbc41ab96dbae5b55708b2e42eec22fd53
Author: Alex Brainman <alex.brainman@gmail.com>
Date:   Fri Feb 10 09:14:00 2012 +1100

    cmd/dist: do not use HEAP_GENERATE_EXCEPTIONS flag
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/5650048

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

https://github.com/golang/go/commit/eaf640dbc41ab96dbae5b55708b2e42eec22fd53

元コミット内容

cmd/dist: do not use HEAP_GENERATE_EXCEPTIONS flag

変更の背景

このコミットは、Go言語のビルドシステムの一部である cmd/dist がWindows環境でメモリを管理する方法を修正しています。cmd/dist は、Goのソースコードから実行可能ファイルをビルドするために使用されるツールです。Windowsでは、メモリ管理にWin32 APIのヒープ関数(HeapAlloc, HeapReAlloc など)が使用されます。

HeapReAlloc 関数は、既存のメモリブロックのサイズを変更するために使用されます。通常、この関数が失敗した場合(例えば、メモリ不足の場合)、NULL を返します。しかし、HEAP_GENERATE_EXCEPTIONS フラグを指定すると、失敗時に NULL を返す代わりに、構造化例外(STATUS_NO_MEMORYSTATUS_ACCESS_VIOLATION など)を発生させるようになります。

このコミットの背景には、おそらく HEAP_GENERATE_EXCEPTIONS フラグの使用が、cmd/dist の特定の状況下で予期せぬ挙動やクラッシュを引き起こしていた可能性があります。Goのランタイムやツールチェーンは、クロスプラットフォームでの安定性と予測可能性を重視するため、特定のOS固有の例外処理メカニズムに依存するのではなく、より一般的なエラーハンドリング(NULL チェックなど)に切り替えることが望ましいと判断されたと考えられます。

前提知識の解説

Win32 APIとヒープメモリ管理

Windowsオペレーティングシステムでは、アプリケーションが動的にメモリを割り当てるためにWin32 APIが提供されています。その中でも、ヒープメモリ管理は重要な概念です。

  • ヒープ (Heap): プログラムが実行時に動的にメモリを割り当てたり解放したりするための領域です。C/C++における malloc/freenew/delete に相当する機能を提供します。
  • GetProcessHeap(): 現在のプロセスのデフォルトヒープへのハンドルを取得する関数です。ほとんどのアプリケーションは、このデフォルトヒープを使用します。
  • HeapReAlloc(): 指定されたヒープから割り当てられたメモリブロックのサイズを変更する関数です。
    • 第一引数: ヒープのハンドル。
    • 第二引数: フラグ。メモリ再割り当ての動作を制御します。
    • 第三引数: 再割り当てする既存のメモリブロックへのポインタ。
    • 第四引数: 新しいメモリブロックのサイズ(バイト単位)。
    • 成功した場合、再割り当てされたメモリブロックへのポインタを返します。失敗した場合、NULL を返します。
  • HEAP_GENERATE_EXCEPTIONS フラグ: HeapAlloc, HeapReAlloc, HeapCreate などのヒープ関数で使用できるフラグです。このフラグが設定されている場合、メモリ割り当てや再割り当てが失敗した際に、関数が NULL を返す代わりに、構造化例外(Structured Exception Handling, SEH)を発生させます。
    • STATUS_NO_MEMORY: メモリ不足の場合に発生する例外。
    • STATUS_ACCESS_VIOLATION: ヒープ破損など、不正なメモリ操作があった場合に発生する例外。
    • このフラグを使用すると、エラー処理を例外ベースで行うことができますが、例外処理のオーバーヘッドや、予期せぬ例外がプログラムの安定性を損なう可能性もあります。

構造化例外処理 (SEH)

Windowsの構造化例外処理 (SEH) は、ハードウェア例外(ゼロ除算、アクセス違反など)やソフトウェア例外(RaiseException で明示的に発生させる例外)を処理するためのメカニズムです。C/C++では __try, __except, __finally ブロックを使用して例外を捕捉・処理します。

HEAP_GENERATE_EXCEPTIONS フラグは、このSEHメカニズムを利用してメモリ割り当ての失敗を通知します。このフラグを使用しない場合、関数はエラーコード(この場合は NULL)を返すため、呼び出し元は明示的に戻り値をチェックしてエラーを処理する必要があります。

技術的詳細

変更が行われたファイルは src/cmd/dist/windows.c です。このファイルは、GoのディストリビューションツールがWindows上で動作する際の、OS固有の処理(特にメモリ管理)を実装しています。

問題の箇所は xrealloc 関数です。この関数は、Goのツールチェーン内で使用されるカスタムのメモリ再割り当て関数であり、内部的にWin32 APIの HeapReAlloc を呼び出しています。

元のコードでは、HeapReAlloc の呼び出しに HEAP_GENERATE_EXCEPTIONS フラグが渡されていました。

p = HeapReAlloc(HEAP, HEAP_GENERATE_EXCEPTIONS, p, n);

このフラグが存在すると、HeapReAlloc がメモリ再割り当てに失敗した場合、STATUS_NO_MEMORY などの例外が発生します。しかし、Goのツールチェーンの設計思想や、より移植性の高いエラーハンドリングの観点から、この例外ベースの挙動は望ましくないと判断された可能性があります。

変更後のコードでは、HEAP_GENERATE_EXCEPTIONS フラグが 0 に置き換えられています。

p = HeapReAlloc(HEAP, 0, p, n);

0 はフラグが何も設定されていないことを意味します。これにより、HeapReAlloc が失敗した場合、例外を発生させる代わりに NULL を返すようになります。その後のコードでは、p == nil(Goの NULL に相当)をチェックしてメモリ不足を検出しています。

if(p == nil)
    fatal("out of memory reallocating %d", n);

この変更により、メモリ再割り当ての失敗は、Win32の構造化例外ではなく、Goのツールチェーン内で一般的な NULL チェックと fatal 関数によるエラー報告という、より統一された方法で処理されるようになります。これは、コードの可読性、保守性、そしてクロスプラットフォーム互換性の向上に寄与します。

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

--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -735,7 +735,7 @@ xrealloc(void *p, int n)
 		return xmalloc(n);
 	if(HEAP == INVALID_HANDLE_VALUE)
 		HEAP = GetProcessHeap();
-	p = HeapReAlloc(HEAP, HEAP_GENERATE_EXCEPTIONS, p, n);
+	p = HeapReAlloc(HEAP, 0, p, n);
 	if(p == nil)
 		fatal("out of memory reallocating %d", n);
 	return p;

コアとなるコードの解説

変更されたのは src/cmd/dist/windows.c ファイル内の xrealloc 関数です。

xrealloc 関数は、Goのビルドツールが内部的に使用するメモリ再割り当てのラッパー関数です。

  1. if(p == nil): 渡されたポインタ pnilNULL)の場合、これは新しいメモリブロックの割り当て要求と見なされ、xmalloc(n) を呼び出して新しいメモリを割り当てます。
  2. if(HEAP == INVALID_HANDLE_VALUE): HEAP 変数がまだ初期化されていない場合(INVALID_HANDLE_VALUE は無効なハンドルを示す定数)、GetProcessHeap() を呼び出して現在のプロセスのデフォルトヒープのハンドルを取得し、HEAP に格納します。これにより、以降のヒープ操作で同じヒープが使用されます。
  3. p = HeapReAlloc(HEAP, 0, p, n);: ここが変更の核心です。
    • 以前は HEAP_GENERATE_EXCEPTIONS フラグが第二引数に渡されていましたが、これが 0 に変更されました。
    • これにより、HeapReAlloc がメモリ再割り当てに失敗した場合、例外を発生させる代わりに NULL を返します。
  4. if(p == nil): HeapReAlloc の呼び出し後、返されたポインタ pnil であるかどうかをチェックします。
    • もし nil であれば、メモリ再割り当てが失敗したことを意味します。
    • この場合、fatal("out of memory reallocating %d", n); を呼び出して、指定されたサイズのメモリ再割り当てに失敗したことを示す致命的なエラーメッセージを出力し、プログラムを終了します。
  5. return p;: 成功した場合、再割り当てされたメモリブロックへのポインタを返します。

この変更により、xrealloc 関数は、Windowsの構造化例外メカニズムに依存することなく、HeapReAlloc の戻り値(NULL)を直接チェックすることでメモリ不足エラーを処理するようになりました。これは、Goのツールチェーン全体でより一貫性のあるエラーハンドリングパターンに沿ったものです。

関連リンク

  • Go Issue Tracker (CL 5650048): https://golang.org/cl/5650048 (コミットメッセージに記載されている変更リストへのリンク)

参考にした情報源リンク