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

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

このコミットは、Go言語のコンパイラ(gc)における文字エスケープシーケンスの処理を改善するものです。具体的には、long型で扱っていた文字コードをulong(符号なし長整数)型に変更することで、より広範囲のUnicode文字、特に\U形式の32ビットUnicodeエスケープシーケンスを正しく処理できるように修正しています。コミットメッセージの'\Ucafebabe'は、この変更が\U形式のエスケープシーケンスのテストまたは修正に関連していることを示唆しています。

コミット

  • コミットハッシュ: b6218e690738b2563598a150c08b17eb6069444b
  • 作者: Ken Thompson ken@golang.org
  • コミット日時: 2008年6月6日 金曜日 17:42:03 -0700

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

https://github.com/golang/go/commit/b6218e690738b2563598a150c08b17eb6069444b

元コミット内容

'\\Ucafebabe'

SVN=121562

変更の背景

この変更は、Go言語の初期開発段階において、コンパイラが文字列リテラル内のUnicodeエスケープシーケンス、特に\Uで始まる32ビットのUnicodeコードポイントを正しく解釈・処理できるようにするために行われました。

Go言語は設計当初からUnicodeを完全にサポートすることを目指しており、文字列はUTF-8でエンコードされます。文字列リテラル内では、\uXXXX(16ビット)や\UXXXXXXXX(32ビット)といったUnicodeエスケープシーケンスを使用して任意のUnicode文字を表現できます。

しかし、従来のlong型(多くのシステムで32ビット符号付き整数)で文字コードを扱う場合、0x7FFFFFFF(約21億)を超える値、つまりUnicodeのU+80000000からU+FFFFFFFFまでの範囲のコードポイントを表現できません。cafebabeという16進数は0xcafebabeであり、これは0x7FFFFFFFを大きく超える値です。このような大きなコードポイントをlong型で扱うと、符号ビットが反転して負の値として解釈されたり、オーバーフローが発生したりする可能性があります。

このコミットは、このような問題を解決し、Goコンパイラがすべての有効なUnicodeコードポイント(U+000000からU+10FFFFまで)を正しく処理できるようにするために、文字コードを格納する変数の型をlongからulong(符号なし長整数)に変更しました。ulong型は、その名の通り符号なしであるため、より大きな正の整数値を表現できます。

前提知識の解説

1. UnicodeとUTF-8

  • Unicode: 世界中の文字を統一的に扱うための文字コードの国際標準です。各文字には一意の「コードポイント」(例: U+0041は'A')が割り当てられています。
  • UTF-8: Unicodeのコードポイントをバイト列にエンコードするための可変長文字エンコーディング方式です。ASCII互換性があり、Webで最も広く使われています。Go言語の文字列は内部的にUTF-8で表現されます。

2. Unicodeエスケープシーケンス

プログラミング言語の文字列リテラルでは、直接入力できない文字や特殊文字を表現するためにエスケープシーケンスが使われます。Unicode文字の場合、Goでは以下の形式が使われます。

  • \uXXXX: 16進数4桁で指定されるUnicodeコードポイント(例: \u0041は'A')。
  • \UXXXXXXXX: 16進数8桁で指定されるUnicodeコードポイント(例: \U0001F600は😀)。これは、U+FFFFを超えるコードポイント(サロゲートペアではない真の32ビットコードポイント)を表現するために必要です。

3. long型とulong

C言語(Goコンパイラの一部はC言語で書かれていた時期があるため、この文脈で重要)やGo言語の初期のコンパイラ実装で使われる可能性のあるデータ型です。

  • long: 符号付き長整数型です。通常32ビットまたは64ビットで、正の値と負の値を表現できます。32ビットの場合、表現できる最大値は2^31 - 1です。
  • ulong: 符号なし長整数型です。通常32ビットまたは64ビットで、負の値を表現できず、0以上の値のみを表現できます。32ビットの場合、表現できる最大値は2^32 - 1です。

Unicodeのコードポイントは常に0以上の値であり、最大でU+10FFFF(16進数で0x10FFFF)まで存在します。これは32ビット符号なし整数で十分に表現できる範囲です。しかし、32ビット符号付き整数では、0x7FFFFFFFを超えるコードポイント(例えば0xCAFEBABE)は正しく扱えません。

4. gcコンパイラ

gcは、Go言語の公式コンパイラツールチェーンの一部であり、Go言語のソースコードを機械語に変換する役割を担っています。このコミットが変更しているsrc/cmd/gc/go.hsrc/cmd/gc/lex.cは、gcコンパイラのヘッダファイルと字句解析(lexical analysis)を担当するソースファイルです。字句解析は、ソースコードをトークン(キーワード、識別子、リテラルなど)に分割する最初の段階であり、文字列リテラル内のエスケープシーケンスの解釈もこの段階で行われます。

技術的詳細

このコミットの核心は、Goコンパイラの字句解析器(lexer)がUnicodeエスケープシーケンスを処理する際に使用する変数のデータ型を、longからulongへ変更した点にあります。

具体的には、以下の関数や変数で型が変更されています。

  1. escchar関数:

    • この関数は、文字列リテラルや文字リテラル内のエスケープシーケンス(例: \n, \t, \uXXXX, \UXXXXXXXX)を実際の文字コードに変換する役割を担っています。
    • 変更前はlong escchar(long e, int *escflg)というシグネチャでしたが、変更後はulong escchar(int e, int *escflg)となりました。
    • 戻り値の型がlongからulongに変更されたことで、0x7FFFFFFFを超えるUnicodeコードポイント(例えば0xCAFEBABEのような値)も正しく符号なしの数値として返すことができるようになります。
    • 第一引数eの型もlongからintに変更されていますが、これはおそらくエスケープシーケンスの開始文字(例: \)を渡すためのものであり、文字コード自体を渡すわけではないため、intで十分と判断されたのでしょう。
  2. yylex関数:

    • この関数は、字句解析器のメインループであり、ソースコードから次のトークンを読み取る役割を担っています。文字列リテラルや文字リテラルを解析する際にescchar関数を呼び出します。
    • 関数内部で文字コードを一時的に保持する変数cc1の型がlongからulongに変更されました。これにより、esccharから返されるulong型の値を正しく受け取り、処理を継続できるようになります。

この変更により、Goコンパイラは、\UXXXXXXXX形式で表現されるすべての有効なUnicodeコードポイント(U+000000からU+10FFFFまで)を、オーバーフローや符号反転の問題なく、正確に数値として解釈し、後続のコンパイル処理に渡すことが可能になりました。これは、Go言語が国際化対応を重視し、Unicodeを第一級市民として扱うという設計思想を反映した重要な修正です。

コミットメッセージの'\Ucafebabe'は、おそらくこの修正をテストするために使用された具体的なUnicodeエスケープシーケンスの例です。0xCAFEBABEは、Javaのクラスファイルのマジックナンバーとしても知られていますが、ここでは単に32ビットの大きなUnicodeコードポイントの例として使われていると考えられます。

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

このコミットでは、以下の2つのファイルが変更されています。

src/cmd/gc/go.h

--- a/src/cmd/gc/go.h
+++ b/src/cmd/gc/go.h
@@ -387,7 +387,7 @@ void	lexinit(void);
 char*	lexname(int);
 long	getr(void);
 int	getnsc(void);
-long	escchar(long, int*);
+ulong	escchar(int, int*);
 int	getc(void);
 void	ungetc(int);
 void	mkpackage(char*);

src/cmd/gc/lex.c

--- a/src/cmd/gc/lex.c
+++ b/src/cmd/gc/lex.c
@@ -171,7 +171,7 @@ cannedimports(void)\n long\n yylex(void)\n {\n-\tlong c, c1;\n+\tulong c, c1;\n \tchar *cp;\n \tRune rune;\n \tint escflag;\n@@ -696,10 +696,10 @@ getnsc(void)\n }\n \n \n-long\n-escchar(long e, int *escflg)\n+ulong\n+escchar(int e, int *escflg)\n {\n-\tlong c, l;\n+\tulong c, l;\n \tint i;\n \n \t*escflg = 0;\n@@ -753,7 +753,7 @@ loop:\n \n \tdefault:\n \t\tif(c != e)\n-\t\twarn(\"unknown escape sequence: %c\", c);\n+\t\t\twarn(\"unknown escape sequence: %c\", c);\
 \t}\n \treturn c;\n \n```

## コアとなるコードの解説

### `src/cmd/gc/go.h` の変更

*   `long escchar(long, int*);` から `ulong escchar(int, int*);` へ変更。
    *   これは`escchar`関数のプロトタイプ宣言です。
    *   関数の戻り値の型が`long`(符号付き長整数)から`ulong`(符号なし長整数)に変更されました。これにより、`escchar`関数が返す文字コードが、`0x7FFFFFFF`を超えるような大きなUnicodeコードポイントであっても、負の値として誤って解釈されることなく、正しく符号なしの数値として扱われるようになります。
    *   第一引数の型が`long`から`int`に変更されていますが、これはエスケープシーケンスの開始文字(例: `\`)を渡すためのものであり、文字コード自体を渡すわけではないため、`int`で十分です。

### `src/cmd/gc/lex.c` の変更

1.  **`yylex`関数内の変数宣言の変更**:
    ```diff
    -\tlong c, c1;
    +\tulong c, c1;
    ```
    *   `yylex`関数は字句解析の主要な関数であり、ソースコードから文字を読み取り、トークンを生成します。
    *   この関数内で文字コードを一時的に保持する変数`c`と`c1`の型が`long`から`ulong`に変更されました。これにより、`escchar`関数から返される`ulong`型の値を正しく受け取り、オーバーフローや符号反転の問題なく処理できるようになります。

2.  **`escchar`関数の定義の変更**:
    ```diff
    -long
    -escchar(long e, int *escflg)
    +ulong
    +escchar(int e, int *escflg)
    ```
    ```diff
    -\tlong c, l;
    +\tulong c, l;
    ```
    *   `escchar`関数の定義自体も、プロトタイプ宣言に合わせて戻り値の型と第一引数の型が変更されました。
    *   関数内部で文字コードを扱う変数`c`と`l`の型も`long`から`ulong`に変更されました。これにより、エスケープシーケンスから得られたUnicodeコードポイントが、関数内部で処理される際も常に符号なしの数値として扱われることが保証されます。

3.  **警告メッセージのインデント修正**:
    ```diff
    -\t\twarn("unknown escape sequence: %c", c);
    +\t\t\twarn("unknown escape sequence: %c", c);
    ```
    *   これは機能的な変更ではなく、単に警告メッセージのインデントが1つ増えただけの整形上の変更です。おそらく、コードの可読性やスタイルガイドに合わせたものと考えられます。

これらの変更により、Goコンパイラは、`\UXXXXXXXX`形式のUnicodeエスケープシーケンスを含む文字列リテラルを、Unicodeの全範囲(U+000000からU+10FFFF)にわたって正確に解析し、適切な文字コードを生成できるようになりました。これは、Go言語の国際化対応とUnicodeサポートの堅牢性を高める上で不可欠な修正です。

## 関連リンク

*   Go言語の仕様: [https://go.dev/ref/spec](https://go.dev/ref/spec)
    *   特に「String literals」のセクションで、エスケープシーケンスについて記述されています。
*   Unicode Consortium: [https://home.unicode.org/](https://home.unicode.org/)
*   UTF-8: [https://ja.wikipedia.org/wiki/UTF-8](https://ja.wikipedia.org/wiki/UTF-8)

## 参考にした情報源リンク

*   Go言語のソースコード(GitHubリポジトリ)
*   C言語のデータ型に関する一般的な知識
*   UnicodeおよびUTF-8に関する一般的な知識
*   Javaのクラスファイルフォーマット(`0xCAFEBABE`のマジックナンバーについて)