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

[インデックス 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) を受け取り、フォーマットされた文字列を指定されたバッファに書き込む関数です。バッファのサイズを指定できるため、バッファオーバーフローを防ぐのに役立ちます。最初の引数に NULL0 を渡すことで、必要なバッファサイズを計算するために使用されることがあります。
  • vfprintf: 可変引数リスト (va_list) を受け取り、フォーマットされた文字列を指定されたファイルストリーム (FILE*) に出力する関数です。fprintf の可変引数版です。

可変引数リスト (Variadic Arguments)

C言語では、関数の引数の数が固定されていない「可変引数」を持つ関数を定義できます。printfscanf などがその例です。可変引数を扱うためには、以下のマクロが使用されます。

  • va_list: 可変引数リストへのポインタを保持するための型です。
  • va_start(va_list ap, last_arg): 可変引数リストの処理を開始します。apva_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環境における xprintferrprintf の実装を、より堅牢な 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);                         // バッファを解放
}

この実装では、以下の手順で出力を行っていました。

  1. va_start で可変引数リストを初期化。
  2. vsnprintf(NULL, 0, fmt, arg) を呼び出して、フォーマットされた文字列を格納するために必要なバッファサイズ n を計算。
  3. xmalloc でそのサイズのメモリを動的に割り当て、ポインタ p に格納。
  4. 再度 vsnprintf(p, n+1, fmt, arg) を呼び出して、実際にフォーマットされた文字列を p が指すバッファに書き込む。
  5. va_end で可変引数リストをクリーンアップ。
  6. GetStdHandle(STD_OUTPUT_HANDLE) で標準出力のハンドルを取得。
  7. WriteFile を使用して、バッファ p の内容を標準出力に書き込む。
  8. 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) に変更されています。

この新しい実装では、以下の点が異なります。

  1. vsnprintf を使用したバッファサイズの計算と動的なメモリ割り当て・解放が不要になりました。
  2. Windows APIの WriteFile を直接呼び出す代わりに、C標準ライブラリの vprintf (または vfprintf) を使用しています。

vprintfvfprintf は、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 ファイル内の xprintferrprintf 関数の実装が大幅に簡素化されたことを示しています。

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) に関する一般的な知識。