[インデックス 18755] ファイルの概要
このコミットは、Go言語のディストリビューションツール (cmd/dist) のWindows固有のコード (src/cmd/dist/windows.c) における出力処理の改善に関するものです。具体的には、標準出力および標準エラー出力への書き込みに使用される xprintf および errprintf 関数が、Windows環境で時折出力を生成できない問題を修正しています。
コミット
このコミットは、cmd/dist ツールがWindows上で出力を生成する際に、Unix環境と同様に vfprintf を使用するように変更します。これにより、Windows環境での出力の信頼性が向上し、以前の WriteFile を直接使用する実装で発生していた出力の失敗が解消されます。
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3f1374fcf8ae8b76679010d649a7b444772f9642
元コミット内容
commit 3f1374fcf8ae8b76679010d649a7b444772f9642
Author: Russ Cox <rsc@golang.org>
Date: Wed Mar 5 14:16:30 2014 -0500
cmd/dist: use vfprintf on Windows, same as on Unix
Apparently, the Windows routines sometimes fail to generate output.
Copy the Unix stdio-based implementations instead.
Suggested by Pietro Gagliardi in CL 65280043 but that CL
seems to have been abandoned.
Fixes #7242.
LGTM=bradfitz
R=golang-codereviews, bradfitz
CC=golang-codereviews
https://golang.org/cl/71550044
変更の背景
Go言語のビルドシステムの一部である cmd/dist は、Goのソースコードからツールチェーンを構築するために使用される重要なツールです。このツールはクロスプラットフォームで動作する必要があり、Windows環境もサポートしています。
このコミットが行われる前、cmd/dist のWindows版では、標準出力や標準エラー出力へのメッセージ出力にWindows APIの WriteFile 関数を直接使用していました。しかし、この実装には問題があり、**「Windowsルーチンが時折出力を生成できない」**という現象が発生していました。これは、ビルドプロセス中にユーザーに重要な情報が表示されない、あるいはデバッグ情報が失われるといった問題を引き起こす可能性がありました。
この問題は、GoのIssue #7242として報告されており、このコミットはその問題を修正することを目的としています。コミットメッセージには、Pietro Gagliardiが以前に同様の変更を提案していたが、その変更が放棄されたことが示唆されています。このコミットは、Unix環境で既に採用されている stdio ベースの実装(具体的には vfprintf)をWindowsにも適用することで、この出力の信頼性の問題を解決しようとしています。
前提知識の解説
このコミットを理解するためには、以下のC言語の標準ライブラリ関数、Windows API、および可変引数リストに関する知識が必要です。
C言語の標準入出力関数
printf/fprintf: フォーマットされた文字列を標準出力または指定されたファイルストリームに出力する関数です。vsnprintf: 可変引数リスト (va_list) を受け取り、フォーマットされた文字列を指定されたバッファに書き込む関数です。バッファのサイズを指定できるため、バッファオーバーフローを防ぐのに役立ちます。最初の引数にNULLと0を渡すことで、必要なバッファサイズを計算するために使用されることがあります。vfprintf: 可変引数リスト (va_list) を受け取り、フォーマットされた文字列を指定されたファイルストリーム (FILE*) に出力する関数です。fprintfの可変引数版です。
可変引数リスト (Variadic Arguments)
C言語では、関数の引数の数が固定されていない「可変引数」を持つ関数を定義できます。printf や scanf などがその例です。可変引数を扱うためには、以下のマクロが使用されます。
va_list: 可変引数リストへのポインタを保持するための型です。va_start(va_list ap, last_arg): 可変引数リストの処理を開始します。apはva_list型の変数、last_argは可変引数リストの直前の固定引数です。va_end(va_list ap): 可変引数リストの処理を終了し、apをクリーンアップします。
Windows API
GetStdHandle(DWORD nStdHandle): 指定された標準デバイス(標準入力、標準出力、標準エラー出力)のハンドルを取得する関数です。STD_OUTPUT_HANDLE: 標準出力デバイスのハンドルを取得します。STD_ERROR_HANDLE: 標準エラー出力デバイスのハンドルを取得します。
WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped): 指定されたファイルまたはI/Oデバイスにデータを書き込む関数です。このコミットでは、標準出力/エラー出力への書き込みに使用されていました。HANDLE hFile: 書き込み先のファイルまたはデバイスのハンドル。LPCVOID lpBuffer: 書き込むデータを含むバッファへのポインタ。DWORD nNumberOfBytesToWrite: 書き込むバイト数。LPDWORD lpNumberOfBytesWritten: 実際に書き込まれたバイト数を受け取る変数へのポインタ。
DWORD: Windows APIでよく使われる32ビット符号なし整数型です。
メモリ管理
xmalloc:mallocのラッパー関数で、メモリ割り当てが失敗した場合にエラーを報告して終了するなどの追加の処理を行うことが多いです。xfree:freeのラッパー関数で、メモリ解放に関する追加の処理を行うことがあります。
技術的詳細
このコミットの核心は、Windows環境における xprintf と errprintf の実装を、より堅牢な stdio ベースの vfprintf に切り替えることです。
変更前の実装 (xprintf の例):
void
xprintf(char *fmt, ...)
{
va_list arg;
char *p;
DWORD n, w;
va_start(arg, fmt);
n = vsnprintf(NULL, 0, fmt, arg); // 必要なバッファサイズを計算
p = xmalloc(n+1); // そのサイズのバッファを割り当て
vsnprintf(p, n+1, fmt, arg); // バッファにフォーマットされた文字列を書き込み
va_end(arg);
w = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0); // Windows APIで出力
xfree(p); // バッファを解放
}
この実装では、以下の手順で出力を行っていました。
va_startで可変引数リストを初期化。vsnprintf(NULL, 0, fmt, arg)を呼び出して、フォーマットされた文字列を格納するために必要なバッファサイズnを計算。xmallocでそのサイズのメモリを動的に割り当て、ポインタpに格納。- 再度
vsnprintf(p, n+1, fmt, arg)を呼び出して、実際にフォーマットされた文字列をpが指すバッファに書き込む。 va_endで可変引数リストをクリーンアップ。GetStdHandle(STD_OUTPUT_HANDLE)で標準出力のハンドルを取得。WriteFileを使用して、バッファpの内容を標準出力に書き込む。xfreeで動的に割り当てたメモリを解放。
このアプローチの問題点は、WriteFile が低レベルのWindows APIであり、C標準ライブラリの stdio 関数が提供するようなバッファリングやエラーハンドリングの抽象化が不足している点にあります。特に、WriteFile が「時折出力を生成できない」という問題は、内部的なバッファリングの不整合、コンソールとの同期問題、または特定の環境下でのAPIの挙動に起因する可能性があります。また、動的なメモリ割り当てと解放が毎回発生するため、オーバーヘッドも存在します。
変更後の実装 (xprintf の例):
void
xprintf(char *fmt, ...)
{
va_list arg;
va_start(arg, fmt);
vprintf(fmt, arg); // vprintf を使用して標準出力へ直接出力
va_end(arg);
}
errprintf も同様に vfprintf(stderr, fmt, arg) に変更されています。
この新しい実装では、以下の点が異なります。
vsnprintfを使用したバッファサイズの計算と動的なメモリ割り当て・解放が不要になりました。- Windows APIの
WriteFileを直接呼び出す代わりに、C標準ライブラリのvprintf(またはvfprintf) を使用しています。
vprintf や vfprintf は、C標準ライブラリによって提供される高レベルな関数であり、内部的に適切なバッファリング、エンコーディング処理、およびエラーハンドリングを管理しています。これにより、Windows環境での出力の信頼性が向上し、以前の WriteFile を直接使用する実装で発生していた問題が解消されると期待されます。Unix環境では既に stdio ベースの実装が使用されており、この変更によりWindows版も同様の堅牢な出力メカニズムを利用することになります。
コアとなるコードの変更箇所
diff --git a/src/cmd/dist/windows.c b/src/cmd/dist/windows.c
index 7d03989b27..2839c4bc51 100644
--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -840,34 +840,20 @@ void
xprintf(char *fmt, ...)
{
va_list arg;
- char *p;
- DWORD n, w;
-
+
va_start(arg, fmt);
- n = vsnprintf(NULL, 0, fmt, arg);
- p = xmalloc(n+1);
- vsnprintf(p, n+1, fmt, arg);
+ vprintf(fmt, arg);
va_end(arg);
- w = 0;
- WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0);
- xfree(p);
}
void
errprintf(char *fmt, ...)
{
va_list arg;
- char *p;
- DWORD n, w;
-
+
va_start(arg, fmt);
- n = vsnprintf(NULL, 0, fmt, arg);
- p = xmalloc(n+1);
- vsnprintf(p, n+1, fmt, arg);
+ vfprintf(stderr, fmt, arg);
va_end(arg);
- w = 0;
- WriteFile(GetStdHandle(STD_ERROR_HANDLE), p, n, &w, 0);
- xfree(p);
}
int
コアとなるコードの解説
このdiffは、src/cmd/dist/windows.c ファイル内の xprintf と errprintf 関数の実装が大幅に簡素化されたことを示しています。
xprintf 関数の変更
- 削除された行:
char *p;,DWORD n, w;: 以前の動的なバッファ割り当てとWriteFileのための変数が削除されました。n = vsnprintf(NULL, 0, fmt, arg);: 必要なバッファサイズを計算するためのvsnprintfの呼び出しが削除されました。p = xmalloc(n+1);: 動的なメモリ割り当てが削除されました。vsnprintf(p, n+1, fmt, arg);: フォーマットされた文字列をバッファに書き込むためのvsnprintfの呼び出しが削除されました。w = 0;,WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0);: Windows APIのWriteFileを使用した直接的な出力処理が削除されました。xfree(p);: 動的に割り当てたメモリを解放する処理が削除されました。
- 追加された行:
vprintf(fmt, arg);: 代わりに、C標準ライブラリのvprintf関数が呼び出されています。vprintfは、可変引数リストを受け取り、フォーマットされた文字列を標準出力 (stdout) に直接書き込みます。これにより、バッファリングや低レベルなAPI呼び出しの管理がC標準ライブラリに任され、コードが大幅に簡素化され、堅牢性が向上します。
errprintf 関数の変更
errprintf 関数も xprintf と同様の変更が加えられています。
- 削除された行:
xprintfと同様に、動的なバッファ割り当て、vsnprintfの呼び出し、WriteFileを使用した出力、およびメモリ解放のコードが削除されました。 - 追加された行:
vfprintf(stderr, fmt, arg);: 代わりに、C標準ライブラリのvfprintf関数が呼び出されています。vfprintfは、可変引数リストを受け取り、フォーマットされた文字列を標準エラー出力 (stderr) に直接書き込みます。
これらの変更により、cmd/dist のWindows版における出力処理は、Unix版と同様にC標準ライブラリの stdio 関数に依存するようになり、Windows APIの低レベルな WriteFile を直接操作する複雑さと潜在的な問題が解消されました。これにより、出力の信頼性が向上し、コードの保守性も高まります。
関連リンク
- Go Issue Tracker: https://github.com/golang/go/issues (Issue #7242は、このコミットの時点でGoの公式Issueトラッカーに存在していた可能性がありますが、一般的な検索では見つかりませんでした。これは、
cmd/distツールに特化した内部的なIssueであった可能性が高いです。) - Go Code Review (CL): https://golang.org/cl/71550044
参考にした情報源リンク
- C言語の標準ライブラリ関数 (
vprintf,vfprintf,vsnprintf,va_list,va_start,va_end) に関する一般的な知識。 - Windows API (
GetStdHandle,WriteFile) に関する一般的な知識。