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

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

このコミットは、Go言語の標準ライブラリの一部であるsrc/lib/utf8_test.goファイルに対する変更です。具体的には、UTF-8エンコーディングのテストに関連するヘルパー関数Bytes内のバイトスライス([]byte)の初期化方法を修正しています。

コミット

commit 344b16512cd8e6883302617f4727381587d87305
Author: Rob Pike <r@golang.org>
Date:   Tue Jan 6 15:30:07 2009 -0800

    update utf8_test.go
    
    R=rsc
    OCL=22170
    CL=22170

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

https://github.com/golang/go/commit/344b16512cd8e6883302617f4727381587d87305

元コミット内容

    update utf8_test.go
    
    R=rsc
    OCL=22170
    CL=22170

変更の背景

このコミットは、Go言語の非常に初期の段階(2009年1月)に行われたものです。当時のGo言語はまだ開発途上にあり、言語仕様や標準ライブラリのAPIが頻繁に変更されていました。この変更は、バイトスライスの正しい初期化方法に関する修正であり、newmakeというGo言語の2つの異なるメモリ割り当て関数のセマンティクスが、開発初期段階でまだ完全に確立されていなかった、あるいは誤用されていた可能性を示唆しています。

utf8_test.goは、Go言語がUTF-8エンコーディングを正しく処理できることを保証するためのテストファイルです。このテストファイル内のヘルパー関数が正しく動作しない場合、UTF-8関連の機能のテストが正確に行われず、潜在的なバグを見逃す可能性があります。したがって、この修正はテストインフラの信頼性を向上させるための重要な変更でした。

前提知識の解説

Go言語におけるnewmake

Go言語には、メモリを割り当てるための組み込み関数としてnewmakeの2つがあります。これらは似ていますが、異なる目的で使用されます。

  • new(T):

    • Tのゼロ値のためのメモリを割り当て、そのアドレス(*T)を返します。
    • newは、ポインタを返します。
    • 任意の型に対して使用できます。
    • 割り当てられたメモリは、その型のゼロ値で初期化されます(例: 整数は0、文字列は空文字列、スライスはnil)。
    • 例: p := new(int)*p0 である int 型のポインタを返します。
  • make(T, args):

    • スライス([]T)、マップ(map[K]V)、チャネル(chan T)の3つの組み込み参照型のみに使用できます。
    • これらの型を初期化し、使用可能な状態にします。makeはポインタではなく、初期化された(ゼロ値ではない)型Tの値を返します。
    • スライスの場合、make([]T, length, capacity)のように、長さ(length)とオプションで容量(capacity)を指定して、基になる配列を割り当て、スライスヘッダを初期化します。
    • 例: s := make([]int, 10) は、長さ10のintスライスを作成し、その要素をゼロ値で初期化します。

スライス([]byte)の内部構造

Go言語のスライスは、基になる配列への参照、長さ、容量の3つの要素から構成される構造体です。

  • ポインタ: スライスが参照する基になる配列の先頭要素へのポインタ。
  • 長さ(Length): スライスに含まれる要素の数。
  • 容量(Capacity): スライスの基になる配列のサイズ。スライスを拡張できる最大値。

new([]byte, len(s)+1)と書いた場合、これは[]byte型のゼロ値(つまり、ポインタがnil、長さが0、容量が0のスライスヘッダ)へのポインタを割り当てるだけで、実際にlen(s)+1バイトのメモリを持つ基になる配列は割り当てられません。そのため、このスライスに対して要素を書き込もうとすると、ランタイムエラー(パニック)が発生するか、未定義の動作を引き起こす可能性があります。

一方、make([]byte, len(s)+1)と書いた場合、これはlen(s)+1バイトの基になる配列を割り当て、その配列を指すようにスライスヘッダを適切に初期化します。これにより、スライスは指定された長さのバイトを保持する準備が整います。

syscall.StringToBytes (当時のGo言語における内部関数)

このコミットが行われた2009年当時、syscall.StringToBytesという関数がGo言語の内部で使用されていました。この関数は、Goの文字列(不変のバイト列)を、変更可能なバイトスライスに効率的にコピーするために設計されていたと考えられます。現代のGo言語では、文字列をバイトスライスに変換するには、単に[]byte(s)のように型変換を行うのが一般的です。syscallパッケージは、OSのシステムコールにアクセスするためのものであり、このような文字列変換関数がsyscallパッケージ内に存在していたのは、当時のGo言語の設計がまだ流動的であったこと、あるいは特定のプラットフォーム固有の最適化を意図していた可能性を示唆しています。

技術的詳細

このコミットの核心は、Go言語におけるスライスの正しい初期化とメモリ割り当てに関する理解の深化にあります。

元のコード:

b := new([]byte, len(s)+1);

この行は、[]byte型のゼロ値(nilスライス)へのポインタをbに割り当てます。しかし、syscall.StringToBytes関数が期待するのは、実際にメモリが割り当てられ、書き込み可能な状態になっているバイトスライスです。new関数はスライスヘッダのためのメモリを割り当てるだけで、そのスライスが参照する基になるバイト配列は割り当てません。結果として、syscall.StringToBytesがこのbに対してバイトを書き込もうとすると、基になる配列が存在しないため、パニックや不正なメモリアクセスが発生する可能性がありました。

修正後のコード:

b := make([]byte, len(s)+1);

この行は、len(s)+1の長さと容量を持つ新しいバイトスライスをbに割り当てます。make関数は、スライスヘッダだけでなく、そのスライスが参照する基になるバイト配列も実際に割り当て、ゼロ値で初期化します。これにより、blen(s)+1バイトのデータを保持できる状態になり、syscall.StringToBytes関数が安全に文字列のバイトをコピーできるようになります。

この変更は、Go言語の初期開発段階における、newmakeのセマンティクスに関する理解の成熟を示すものです。特に、スライス、マップ、チャネルといった参照型を正しく初期化するためにはmakeを使用する必要があるという、Go言語の基本的な設計原則が明確になったことを意味します。

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

--- a/src/lib/utf8_test.go
+++ b/src/lib/utf8_test.go
@@ -45,7 +45,7 @@ var utf8map = []Utf8Map {
 }
 
 func Bytes(s string) []byte {
-	b := new([]byte, len(s)+1);
+	b := make([]byte, len(s)+1);
 	if !syscall.StringToBytes(b, s) {
 		panic("StringToBytes failed");
 	}

コアとなるコードの解説

変更はsrc/lib/utf8_test.goファイル内のBytes関数にあります。

Bytes関数は、入力として文字列sを受け取り、その文字列のバイト表現を含むバイトスライスを返すことを目的としたヘルパー関数です。

  • 変更前: b := new([]byte, len(s)+1);

    • この行は、[]byte型のゼロ値(nilスライス)へのポインタをbに割り当てようとしていました。しかし、newはスライス自体を初期化して基になる配列を割り当てるわけではありません。そのため、bは有効なバイトスライスとして機能せず、次のsyscall.StringToBytes呼び出しで問題を引き起こす可能性がありました。
  • 変更後: b := make([]byte, len(s)+1);

    • この行は、len(s)+1の長さと容量を持つ新しいバイトスライスをbに割り当てます。make関数は、スライスヘッダを適切に初期化し、指定された長さの基になるバイト配列を割り当てます。これにより、bは文字列のバイトを格納するのに十分なメモリを持つ、完全に機能するバイトスライスになります。
  • if !syscall.StringToBytes(b, s):

    • この行は、文字列sのバイトを、新しく作成されたバイトスライスbにコピーしようとします。syscall.StringToBytesが成功しなかった場合(例えば、bが有効なスライスでなかった場合)、パニックを発生させます。この修正により、bが常に有効なスライスとしてsyscall.StringToBytesに渡されることが保証されます。

この修正は、Go言語の初期段階における、スライスとメモリ割り当てのセマンティクスに関する重要な学習と改善を反映しています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコードリポジトリ: https://github.com/golang/go
  • Go言語のnewmakeに関する一般的な解説記事(多数存在するため、特定のURLは挙げませんが、Go言語の基本的な学習リソースを参照しました)
  • Go言語の初期のコミット履歴(GitHub上で直接参照)