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

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

このコミットは、Go言語のビルドツールである cmd/distREADME ファイルに含まれるコード例の誤りを修正するものです。具体的には、スタック上に確保されたバッファ (Bufs) やベクタ (Vecs) を扱う際のコード例において、誤った変数 b1 を再利用していた箇所を、正しく別の変数 b2 を使用するように修正しています。これにより、コード例が意図する動作を正確に反映するようになりました。

コミット

  • コミットハッシュ: 6adbc545f69a924fc5bd98eae14bda80c47d118b
  • 作者: Russ Cox rsc@golang.org
  • コミット日時: 2013年1月30日 水曜日 08:46:50 -0800
  • コミットメッセージ:
    cmd/dist: fix code example in README
    
    Fixes #4729.
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/7232060
    

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

https://github.com/golang/go/commit/6adbc545f69a924fc5bd98eae14bda80c47d118b

元コミット内容

cmd/dist: fix code example in README

Fixes #4729.

R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/7232060

変更の背景

この変更は、Go言語のIssueトラッカーで報告されたIssue #4729に対応するものです。Issue #4729は、「cmd/distREADME にある bprintf の例が間違っている」という内容でした。

cmd/dist はGo言語のブートストラッププロセスで使用されるツールであり、その README には、Goの内部的な低レベルなコードで使われる Bufs (バッファ) や Vecs (ベクタ) といったデータ構造をスタック上で効率的に扱うためのコード例が記載されていました。しかし、このコード例において、2つ目の文字列をベクタに追加する際に、最初の文字列を格納したのと同じバッファ変数 b1 を再利用していました。

vadd 関数は、引数として渡されたバッファの内容のコピーを取るように設計されていますが、同じバッファを再利用して異なる内容を書き込むと、意図しない結果(例えば、ベクタ内の両方のエントリが最後の内容を指してしまうなど)を招く可能性があります。この誤った例は、読者がGoの内部的なバッファ管理について誤解する原因となるため、修正が必要とされました。

前提知識の解説

このコミットを理解するためには、以下の概念について知っておく必要があります。

  • cmd/dist: Go言語のソースコードをビルドするための内部ツール群の一部です。Goのコンパイラやツールチェイン自体をビルドする際に使用されます。
  • README ファイル: ソフトウェアプロジェクトのルートディレクトリやサブディレクトリに置かれる慣習的なファイルで、そのディレクトリやプロジェクトに関する基本的な情報、使い方、ビルド方法などが記述されています。
  • スタック割り当て (Stack Allocation): プログラムの実行中に、関数呼び出しやローカル変数のためにメモリがスタック上に割り当てられることです。スタックはLIFO (Last-In, First-Out) 構造で、非常に高速なメモリ割り当て・解放が可能です。Go言語では、コンパイラが変数をスタックに割り当てるかヒープに割り当てるかを自動的に決定します(エスケープ解析)。
  • BufsVecs: これらはGo言語の内部コード(特にコンパイラやランタイム)で使われる、低レベルなバッファとベクタの抽象化です。C言語のコードでよく見られるような、固定サイズのバッファや動的配列を効率的に扱うためのものです。
    • Bufs (Buffer): 文字列やバイト列を一時的に保持するためのバッファ構造体。
    • Vecs (Vector): 複数の要素(この場合は文字列のバッファ)を格納するための動的配列構造体。
  • bprintf(&b, "format", ...): Buf 型の変数 b にフォーマットされた文字列を書き込む関数です。C言語の sprintf に似ています。
  • bstr(&b): Buf 型の変数 b の内容を文字列として(または文字列へのポインタとして)返す関数です。
  • vadd(&v, element): Vec 型の変数 v に要素 element を追加する関数です。この例では、bstr(&b) で得られた文字列を Vec に追加しています。
  • bfree(&b): Buf 型の変数 b に関連付けられたリソースを解放する関数です。スタック割り当ての場合、これは主にポインタをリセットするなどのクリーンアップ操作を意味します。
  • Fixes #XXXX: Gitのコミットメッセージにおける慣習的な記述で、このコミットが指定されたIssue番号のバグを修正したことを示します。GitHubなどのプラットフォームでは、この記述があると自動的にIssueがクローズされることがあります。

技術的詳細

このコミットの技術的な核心は、cmd/dist/README に記載されていたコード例が、スタック上に確保された Buf 型の変数を誤って再利用していた点にあります。

元のコード例は以下のようになっていました。

		bprintf(&b1, "hello, world");
		vadd(&v1, bstr(&b1));  // v1 takes a copy of its argument
		bprintf(&b1, "another string"); // ここでb1を再利用
		vadd(&v1, bstr(&b1));  // v1 now has two strings

ここで問題となるのは、bprintf(&b1, "another string"); の行です。vadd(&v1, bstr(&b1));b1 の内容の「コピー」を取るとコメントされていますが、これは b1 が指すメモリ領域の内容をコピーするという意味であり、b1 自体が指すポインタやその基盤となるバッファが独立して複製されるわけではありません。

もし b1 が内部的に動的に拡張されるバッファであったり、あるいは bstr(&b1)b1 の内部ポインタを直接返していた場合、bprintf(&b1, "another string"); の呼び出しによって b1 の内容が上書きされると、v1 に既に追加された最初の文字列も、新しい「another string」に変わってしまう可能性がありました。これは、vadd が「コピーを取る」というコメントにもかかわらず、実際には参照渡しのような振る舞いをしてしまうという誤解を招くものです。

この問題は、Goの内部コードで使われる BufVec の実装詳細に依存しますが、一般的に、異なる論理的なデータには異なるバッファを使用するのが安全かつ意図を明確にする方法です。

修正後のコードは以下のようになります。

		bprintf(&b1, "hello, world");
		vadd(&v1, bstr(&b1));  // v1 takes a copy of its argument
		bprintf(&b2, "another string"); // 新しい変数b2を使用
		vadd(&v1, bstr(&b2));  // v1 now has two strings

この修正により、b1b2 という2つの独立した Buf 変数が使用されるため、それぞれの bprintf 呼び出しが互いに影響を与えることなく、v1 に「hello, world」と「another string」という2つの異なる文字列が正しく追加されることが保証されます。これは、コード例が示すべき「スタック上の複数のバッファを適切に管理する方法」を正確に表現しています。

この修正は、Goの内部開発者向けのドキュメントの正確性を高め、将来のGoの低レベルコード開発者が同様の誤解をしないようにするための重要な改善です。

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

変更は src/cmd/dist/README ファイルの1箇所のみです。

--- a/src/cmd/dist/README
+++ b/src/cmd/dist/README
@@ -31,8 +31,8 @@ Bufs or Vecs on the stack should be
 		... main code ...
 		bprintf(&b1, "hello, world");
 		vadd(&v1, bstr(&b1));  // v1 takes a copy of its argument
-		bprintf(&b1, "another string");
-		vadd(&v1, bstr(&b1));  // v1 now has two strings
+		bprintf(&b2, "another string");
+		vadd(&v1, bstr(&b2));  // v1 now has two strings
 		
 		bfree(&b1);
 		bfree(&b2);

具体的には、以下の2行が変更されました。

  • - bprintf(&b1, "another string");
  • + bprintf(&b2, "another string");
  • - vadd(&v1, bstr(&b1)); // v1 now has two strings
  • + vadd(&v1, bstr(&b2)); // v1 now has two strings

bprintf の呼び出しとそれに続く vadd の呼び出しで、b1 の代わりに b2 が使用されるようになりました。

コアとなるコードの解説

変更されたコードは、cmd/dist/README にあるC言語風の擬似コード例の一部です。この例は、Goの内部ツールがどのようにスタック上のバッファとベクタを扱うかを示しています。

元のコードでは、b1 という単一のバッファ変数を使って、まず「hello, world」を書き込み、それを v1 というベクタに追加した後、同じ b1 に「another string」を上書きして、再度 v1 に追加していました。

この問題は、vaddbstr(&b1) の返す内容の「コピー」を取るとコメントされているにもかかわらず、もし bstr(&b1)b1 の内部バッファへのポインタを直接返しており、かつ vadd がそのポインタをそのまま保持していた場合、b1 の内容が後から変更されると、v1 に既に追加された最初の要素もその変更の影響を受けてしまうという誤解を招く可能性がありました。

修正後のコードでは、2つ目の文字列を書き込む際に b2 という新しいバッファ変数を使用しています。これにより、b1b2 はそれぞれ独立したメモリ領域を指すため、b1 に書き込まれた「hello, world」と b2 に書き込まれた「another string」が、それぞれ独立した内容として v1 に追加されることが保証されます。

この変更は、コード例の正確性を高め、読者がGoの低レベルなメモリ管理やデータ構造の振る舞いについて誤解するのを防ぐことを目的としています。特に、スタック上のリソースを扱う際には、変数の再利用が意図しない副作用を引き起こさないよう注意が必要であるという教訓を示唆しています。

関連リンク

参考にした情報源リンク

  • Go Issue #4729の議論内容
  • Go言語のソースコード (src/cmd/dist/README および関連する内部コード)
  • 一般的なプログラミングにおけるスタック割り当てとバッファ管理の概念