[インデックス 11924] ファイルの概要
このコミットは、Go言語のディストリビューションツール (cmd/dist
) におけるWindows固有のコード (src/cmd/dist/windows.c
) のバグ修正です。具体的には、xprintf
関数が固定サイズのバッファを使用していたために、出力が途中で切り捨てられる可能性があった問題を解決しています。これにより、dist env
コマンドなどの出力が完全に行われるようになります。
コミット
commit 6fae34db94fe02f60edb207c95037a9bf8611e71
Author: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Date: Wed Feb 15 14:25:27 2012 +1100
cmd/dist: xprintf() can't output whole of 'dist env'.
R=golang-dev, mpimenov, alex.brainman
CC=golang-dev
https://golang.org/cl/5667045
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/6fae34db94fe02f60edb207c95037a9bf8611e71
元コミット内容
cmd/dist: xprintf() can't output whole of 'dist env'.
このコミットメッセージは、cmd/dist
ツール内の xprintf()
関数が、dist env
コマンドの出力全体を正しく表示できないという問題を指摘しています。これは、出力が途中で切り捨てられることを示唆しています。
変更の背景
Go言語のビルドおよびディストリビューションプロセスを管理する cmd/dist
ツールは、様々な環境情報を出力する機能を持っています。Windows環境において、このツールが内部で使用する xprintf
関数が、固定サイズのバッファ (buf[1024]
) を用いてフォーマット済み文字列を生成していました。
この設計には、以下のような潜在的な問題がありました。
- バッファオーバーフロー/出力の切り捨て:
vsnprintf
関数は、指定されたバッファサイズを超えて書き込もうとすると、バッファオーバーフローを引き起こすか、または文字列を切り捨てます。この場合、buf
のサイズが1024バイトに固定されていたため、xprintf
に渡されるフォーマット文字列と可変引数によって生成される最終的な文字列が1024バイトを超えると、出力が途中で切れてしまい、完全な情報が表示されないという問題が発生していました。特にdist env
のように環境変数リストなど、可変長で長い文字列が出力される場合に顕在化しやすかったと考えられます。 - メモリの非効率性: 常に1024バイトのスタックメモリを確保するため、短い文字列の場合には無駄が生じます。
このコミットは、この出力の切り捨て問題を解決し、xprintf
が常に完全な文字列を出力できるようにすることを目的としています。
前提知識の解説
vsnprintf
関数
vsnprintf
はC言語の標準ライブラリ関数で、printf
ファミリーの一つです。可変引数リスト (va_list
) を受け取り、指定されたバッファにフォーマットされた文字列を書き込みます。
- 書式:
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
- 引数:
str
: フォーマットされた文字列を書き込むバッファへのポインタ。size
: バッファの最大サイズ(ヌル終端文字を含む)。format
: フォーマット文字列。ap
: 可変引数リスト。
- 戻り値:
- バッファに書き込まれた(または書き込まれるはずだった)文字数(ヌル終端文字を含まない)。
size
が小さすぎて全ての文字を書き込めなかった場合でも、必要な文字数を返します。- 重要なイディオム:
str
にNULL
を、size
に0
を渡すと、vsnprintf
は実際に書き込みを行わずに、フォーマットされた文字列に必要な文字数(ヌル終端文字を含まない)を返します。この機能は、動的にメモリを確保する際に非常に有用です。
WriteFile
関数 (Windows API)
WriteFile
はWindows APIの一部で、指定されたファイルまたはI/Oデバイスにデータを書き込むために使用されます。
- 書式:
BOOL WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped);
- 引数:
hFile
: 書き込み先のファイルまたはI/Oデバイスのハンドル。このコミットではGetStdHandle(STD_OUTPUT_HANDLE)
を使用しており、これは標準出力(コンソール)へのハンドルを取得します。lpBuffer
: 書き込むデータを含むバッファへのポインタ。nNumberOfBytesToWrite
: 書き込むバイト数。lpNumberOfBytesWritten
: 実際に書き込まれたバイト数を受け取る変数へのポインタ。lpOverlapped
: 非同期I/Oに使用される構造体へのポインタ(同期I/Oの場合はNULL
)。
- 戻り値: 成功した場合は非ゼロ、失敗した場合はゼロ。
xmalloc
および xfree
これらはGoプロジェクト内で定義されているカスタムのメモリ管理関数である可能性が高いです。通常、malloc
や free
のラッパーとして実装され、メモリ確保の失敗時のエラーハンドリングや、デバッグ情報の追加など、プロジェクト固有の要件を満たすために使用されます。この文脈では、xmalloc
はメモリを動的に確保し、xfree
はそのメモリを解放する役割を担います。
cmd/dist
cmd/dist
はGo言語のソースコードリポジトリ内のディレクトリであり、Goのビルドシステムの一部を構成するツール群を含んでいます。Goのコンパイラやツールチェーンのビルド、テスト、インストールなど、Goディストリビューションの管理に関連するタスクを実行します。windows.c
は、Windowsプラットフォームに特化した処理を実装しているファイルです。
技術的詳細
このコミットの核心は、xprintf
関数における文字列バッファの管理方法を、固定サイズから動的サイズへと変更した点にあります。
変更前:
void
xprintf(char *fmt, ...)
{
va_list arg;
static char buf[1024]; // 固定サイズのバッファ
DWORD n;
va_start(arg, fmt);
vsnprintf(buf, sizeof buf, fmt, arg); // バッファサイズ1024でフォーマット
va_end(arg);
n = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &n, 0); // strlen(buf)を使用
}
変更前のコードでは、buf
という1024バイトの静的バッファが宣言されていました。vsnprintf
はこのバッファにフォーマットされた文字列を書き込みますが、もし生成される文字列が1024バイトを超えた場合、文字列は切り捨てられます。WriteFile
関数に渡される長さは strlen(buf)
で計算されますが、これは切り捨てられた文字列の長さしか返さないため、完全な出力は行われませんでした。
変更後:
void
xprintf(char *fmt, ...)
{
va_list arg;
char *p;
DWORD n, w;
va_start(arg, fmt);
n = vsnprintf(NULL, 0, fmt, arg); // 1. 必要なバッファサイズを計算
p = xmalloc(n+1); // 2. 必要なサイズでメモリを動的に確保
vsnprintf(p, n+1, fmt, arg); // 3. 動的に確保したバッファにフォーマット
va_end(arg);
w = 0;
WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0); // 4. 正しい長さnで書き込み
xfree(p); // 5. メモリを解放
}
変更後のコードでは、以下のステップで動的なバッファ管理を行っています。
- 必要なバッファサイズの計算: 最初の
vsnprintf(NULL, 0, fmt, arg)
の呼び出しは、実際に文字列を書き込まずに、フォーマットされた文字列に必要な文字数(ヌル終端文字を含まない)をn
に返します。 - メモリの動的確保:
xmalloc(n+1)
を使用して、計算されたn
の長さにヌル終端文字のための1バイトを加えたサイズのメモリを動的に確保します。これにより、文字列がどんなに長くても、常に十分なサイズのバッファが確保されます。 - 文字列のフォーマット: 2回目の
vsnprintf(p, n+1, fmt, arg)
の呼び出しで、動的に確保されたバッファp
にフォーマットされた文字列を書き込みます。 - 出力:
WriteFile
関数にp
とn
を渡して、標準出力に文字列を書き込みます。ここでn
を使用することで、文字列が切り捨てられることなく、正確な長さで出力されることが保証されます。 - メモリの解放:
xfree(p)
を呼び出して、動的に確保したメモリを解放します。これにより、メモリリークを防ぎます。
この変更により、xprintf
は出力文字列の長さに依存せず、常に完全な内容を標準出力に書き出すことができるようになりました。
コアとなるコードの変更箇所
diff --git a/src/cmd/dist/windows.c b/src/cmd/dist/windows.c
index 1655f77704..aa961eb6cf 100644
--- a/src/cmd/dist/windows.c
+++ b/src/cmd/dist/windows.c
@@ -833,14 +833,17 @@ void
xprintf(char *fmt, ...)
{
va_list arg;
- static char buf[1024];
- DWORD n;
+ char *p;
+ DWORD n, w;
va_start(arg, fmt);
- vsnprintf(buf, sizeof buf, fmt, arg);
+ n = vsnprintf(NULL, 0, fmt, arg);
+ p = xmalloc(n+1);
+ vsnprintf(p, n+1, fmt, arg);
va_end(arg);
- n = 0;
- WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &n, 0);
+ w = 0;
+ WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0);
+ xfree(p);
}
int
コアとなるコードの解説
- static char buf[1024];
- 固定サイズの静的バッファ
buf
の宣言が削除されました。これは、出力文字列の長さに応じて動的にメモリを確保する新しいアプローチに置き換えられます。
- 固定サイズの静的バッファ
- DWORD n;
- 以前のコードで使用されていた
n
変数が削除されました。新しいコードでは、n
は必要な文字列長を格納するために再利用され、w
が実際に書き込まれたバイト数を格納するために導入されます。
- 以前のコードで使用されていた
+ char *p;
- 動的に確保されたバッファへのポインタ
p
が宣言されました。
- 動的に確保されたバッファへのポインタ
+ DWORD n, w;
n
はフォーマットされた文字列の長さを保持し、w
はWriteFile
によって実際に書き込まれたバイト数を保持するための変数として宣言されました。
- vsnprintf(buf, sizeof buf, fmt, arg);
- 固定バッファへの
vsnprintf
呼び出しが削除されました。
- 固定バッファへの
+ n = vsnprintf(NULL, 0, fmt, arg);
- これが変更の核心です。
vsnprintf
にNULL
バッファと0
サイズを渡すことで、フォーマットされた文字列に必要なバイト数(ヌル終端文字を含まない)をn
に取得します。
- これが変更の核心です。
+ p = xmalloc(n+1);
n
で得られた必要なバイト数にヌル終端文字のための1バイトを加えたサイズで、xmalloc
を使ってヒープメモリを動的に確保し、そのポインタをp
に格納します。
+ vsnprintf(p, n+1, fmt, arg);
- 動的に確保されたバッファ
p
に、フォーマットされた文字列を書き込みます。n+1
はバッファのサイズ(ヌル終端文字を含む)です。
- 動的に確保されたバッファ
- n = 0;
- 不要になった初期化が削除されました。
- WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), buf, strlen(buf), &n, 0);
- 固定バッファ
buf
とstrlen(buf)
を使用していたWriteFile
呼び出しが削除されました。
- 固定バッファ
+ w = 0;
WriteFile
のlpNumberOfBytesWritten
引数に渡すためのw
変数の初期化です。
+ WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), p, n, &w, 0);
- 動的に確保されたバッファ
p
と、正確な文字列長n
を使用してWriteFile
を呼び出します。これにより、出力が切り捨てられることなく、完全な文字列が書き込まれます。
- 動的に確保されたバッファ
+ xfree(p);
- 動的に確保したメモリ
p
をxfree
を使って解放します。これにより、メモリリークを防ぎます。
- 動的に確保したメモリ
これらの変更により、xprintf
関数は、出力する文字列の長さに応じて動的にメモリを確保し、常に完全な文字列を標準出力に書き出す、より堅牢で安全な実装になりました。
関連リンク
- Go CL (Code Review) ページ: https://golang.org/cl/5667045
参考にした情報源リンク
vsnprintf
のドキュメント (例: cppreference.com): https://en.cppreference.com/w/c/io/vsnprintfWriteFile
のドキュメント (Microsoft Learn): https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-writefile- Go言語のソースコードリポジトリ (GitHub): https://github.com/golang/go
- Go言語の
cmd/dist
についての一般的な情報 (Goのドキュメントや関連するブログ記事など)