[インデックス 13647] ファイルの概要
このコミットは、Go言語の標準ライブラリであるfmt
パッケージにおけるポインタの書式設定の挙動を変更するものです。具体的には、ポインタを%d
、%o
、%x
、%X
といった整数基数指定の書式動詞で出力する際に、従来のデフォルトであった0x
プレフィックスを抑制し、純粋な数値として扱えるようにする改善が加えられています。これにより、開発者はポインタの値をより柔軟な形式で表示できるようになります。
コミット
commit 3ba0f6daf534721e4e671d00c9d3144296eb9a1a
Author: Rob Pike <r@golang.org>
Date: Fri Aug 17 16:12:25 2012 -0700
fmt: honor integer radix formats (%d etc.) for pointers
Before, pointers always appeared as 0x1234ABCD. This CL
keeps that as the default for %p and %v, but lets explicit
numeric verbs override the default.
Fixes #3936.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/6441152
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/3ba0f6daf534721e4e671d00c9d3144296eb9a1a
元コミット内容
fmt: honor integer radix formats (%d etc.) for pointers
Before, pointers always appeared as 0x1234ABCD. This CL
keeps that as the default for %p and %v, but lets explicit
numeric verbs override the default.
Fixes #3936.
変更の背景
Go言語のfmt
パッケージは、様々なデータ型を文字列にフォーマットするための強力な機能を提供します。しかし、このコミットが適用される以前は、ポインタの書式設定には一貫性のない挙動がありました。具体的には、ポインタは常に%p
や%v
といった書式動詞を使用した場合と同様に、0x
プレフィックスが付加された16進数形式(例: 0x1234ABCD
)で出力されていました。これは、ポインタのメモリアドレスを純粋な数値として、例えば10進数や8進数、あるいはプレフィックスなしの16進数として表示したい場合に不便でした。
この制限は、Issue #3936として報告されており、開発者からの要望があったことが示唆されます。ポインタの値を他の数値型と同様に、指定された基数(10進数、8進数、16進数)で柔軟にフォーマットできるようにすることで、fmt
パッケージの使い勝手と表現力が向上します。このコミットは、この問題を解決し、%d
、%o
、%x
、%X
などの整数基数指定の書式動詞がポインタに対しても期待通りに機能するようにするためのものです。
前提知識の解説
このコミットの理解を深めるために、以下のGo言語の概念とfmt
パッケージの機能について解説します。
Go言語のfmt
パッケージ
fmt
パッケージは、Go言語におけるI/Oフォーマット機能を提供します。C言語のprintf
やscanf
に似た関数群を持ち、様々なデータ型を整形して出力したり、文字列から値を読み取ったりすることができます。主な関数にはfmt.Printf
(標準出力への整形出力)、fmt.Sprintf
(文字列への整形出力)、fmt.Fprint
(指定されたio.Writer
への整形出力)などがあります。
書式動詞 (Format Verbs)
fmt
パッケージでは、値をどのように整形するかを指示するために「書式動詞」と呼ばれる特殊な文字を使用します。
%p
: ポインタの値を16進数で表示します。通常、0x
プレフィックスが付きます。%v
: 値をデフォルトの書式で表示します。ポインタの場合、%p
と同様の形式になります。%d
: 10進数で表示します。%o
: 8進数で表示します。%x
: 16進数で表示します(小文字)。%X
: 16進数で表示します(大文字)。%b
: 2進数で表示します。
ポインタ (Pointers)
Go言語におけるポインタは、変数のメモリアドレスを格納する変数です。C言語のポインタと同様に、間接参照(デリファレンス)によってそのアドレスに格納されている値にアクセスできます。ポインタは、関数間で大きな構造体をコピーせずに渡したり、データ構造を動的に構築したりする際に使用されます。
基数 (Radix)
数値の表現形式における「基数」とは、その数値を構成する桁が取りうる値の数を指します。例えば、10進数は基数10(0-9)、8進数は基数8(0-7)、16進数は基数16(0-9, A-F)です。
reflect
パッケージとreflect.Value
reflect
パッケージは、Goプログラムが実行時に自身の構造を検査(リフレクション)することを可能にします。reflect.Value
は、Goの任意の型の値を抽象的に表現する型です。fmt
パッケージのような汎用的なフォーマット関数は、reflect
パッケージを使用して、与えられた引数の型や値を実行時に動的に調べ、適切な書式設定を適用します。fmtPointer
関数は、reflect.Value
を使用してポインタの値を抽出し、そのアドレスを数値として扱います。
技術的詳細
このコミットの技術的な核心は、src/pkg/fmt/print.go
内のfmtPointer
関数の変更にあります。この関数は、fmt
パッケージがポインタの値を文字列に変換する際の主要なロジックを担っています。
fmtPointer
関数の変更点
-
use0x64
フラグの導入:fmtPointer
関数内にuse0x64
というブーリアン型の変数が導入され、初期値はtrue
に設定されます。このフラグは、ポインタの書式設定において0x
プレフィックスを使用するかどうかを制御します。 -
書式動詞による条件分岐の強化:
switch verb
文が変更され、書式動詞に基づいてuse0x64
フラグの値が動的に設定されるようになりました。case 'p', 'v'
: これらの書式動詞(ポインタのデフォルト表示、汎用表示)の場合、use0x64
はtrue
のままです。これは、%p
や%v
が引き続き0x
プレフィックス付きの16進数形式でポインタを表示することを意味します。case 'b', 'd', 'o', 'x', 'X'
: これらの書式動詞(2進数、10進数、8進数、16進数)の場合、use0x64
はfalse
に設定されます。これがこのコミットの主要な変更点であり、これらの書式動詞が使用された際には0x
プレフィックスが抑制されるようになります。
-
実際の書式設定ロジックの分岐: ポインタの実際の数値が
u
として取得された後、その書式設定を行う部分に条件分岐が追加されました。if use0x64
:use0x64
がtrue
の場合、p.fmt0x64(uint64(u), !p.fmt.sharp)
が呼び出されます。fmt0x64
関数は、0x
プレフィックスを付加して16進数形式で数値を整形する役割を担っています。else
:use0x64
がfalse
の場合、p.fmtUint64(uint64(u), verb, false)
が呼び出されます。fmtUint64
関数は、指定された書式動詞(verb
)に従って符号なし整数を整形しますが、0x
プレフィックスは付加しません。これにより、ポインタの値が純粋な数値として、指定された基数で出力されるようになります。
テストコードの変更点 (src/pkg/fmt/fmt_test.go
)
この変更の正しさを検証するために、テストコードも更新されています。
-
新しいテストケースの追加:
fmttests
配列に、ポインタを整数基数でフォーマットする新しいテストケースが追加されました。{"%d", new(int), "PTR_d"}
: ポインタを10進数で表示。{"%o", new(int), "PTR_o"}
: ポインタを8進数で表示。{"%x", new(int), "PTR_x"}
: ポインタを16進数(小文字)で表示。 これらのテストケースでは、期待される出力文字列にPTR_d
、PTR_o
、PTR_x
といったプレースホルダーが使用されており、テスト実行時に実際のポインタアドレスの数値部分に置き換えられます。
-
TestSprintf
関数のPTR
置換ロジックの改善:TestSprintf
関数内で、ポインタのアドレスをテストの比較対象から除外するために、出力文字列中のポインタ部分をPTR
という文字列に置換するロジックが強化されました。pattern
とchars
変数が導入され、PTR_d
、PTR_o
、PTR_x
といった新しいプレースホルダーに対応できるようになりました。chars
変数は、それぞれの基数(10進数なら0-9
、8進数なら0-7
、16進数なら0-9a-fA-F
)に対応する有効な文字セットを定義します。strings.ContainsRune(chars, rune(c))
を使用することで、出力されたポインタアドレスの各文字が、その基数で有効な文字であるかをより汎用的にチェックできるようになりました。これにより、テストがより堅牢になり、新しい書式設定の挙動を正確に検証できるようになっています。
これらの変更により、fmt
パッケージはポインタの書式設定においてより柔軟な制御を提供し、開発者の期待に応える挙動を実現しています。
コアとなるコードの変更箇所
src/pkg/fmt/print.go
--- a/src/pkg/fmt/print.go
+++ b/src/pkg/fmt/print.go
@@ -585,8 +585,12 @@ func (p *pp) fmtBytes(v []byte, verb rune, goSyntax bool, depth int) {
}
func (p *pp) fmtPointer(value reflect.Value, verb rune, goSyntax bool) {
+ use0x64 := true
switch verb {
-\tcase 'p', 'v', 'b', 'd', 'o', 'x', 'X':
+\tcase 'p', 'v':
+\t // ok
+\tcase 'b', 'd', 'o', 'x', 'X':
+\t use0x64 = false
+\t // ok
default:
\tp.badVerb(verb)
}
@@ -616,7 +620,11 @@ func (p *pp) fmtPointer(value reflect.Value, verb rune, goSyntax bool) {
} else if verb == 'v' && u == 0 {
\tp.buf.Write(nilAngleBytes)
} else {
-\t\tp.fmt0x64(uint64(u), !p.fmt.sharp)
+\t\tif use0x64 {
+\t\t\tp.fmt0x64(uint64(u), !p.fmt.sharp)
+\t\t} else {
+\t\t\tp.fmtUint64(uint64(u), verb, false)
+\t\t}
}
}
src/pkg/fmt/fmt_test.go
--- a/src/pkg/fmt/fmt_test.go
+++ b/src/pkg/fmt/fmt_test.go
@@ -442,6 +442,11 @@ var fmttests = []struct {
{"%v", (*int)(nil), "<nil>"},
{"%v", new(int), "0xPTR"},
+ // %d etc. pointers use specified base.
+ {"%d", new(int), "PTR_d"},
+ {"%o", new(int), "PTR_o"},
+ {"%x", new(int), "PTR_x"},
+
// %d on Stringer should give integer if possible
{"%s", time.Time{}.Month(), "January"},
{"%d", time.Time{}.Month(), "1"},
@@ -471,14 +476,26 @@ func TestSprintf(t *testing.T) {
for _, tt := range fmttests {
s := Sprintf(tt.fmt, tt.val)
if i := strings.Index(tt.out, "PTR"); i >= 0 {
+ pattern := "PTR"
+ chars := "0123456789abcdefABCDEF"
+ switch {
+ case strings.HasPrefix(tt.out[i:], "PTR_d"):
+ pattern = "PTR_d"
+ chars = chars[:10]
+ case strings.HasPrefix(tt.out[i:], "PTR_o"):
+ pattern = "PTR_o"
+ chars = chars[:8]
+ case strings.HasPrefix(tt.out[i:], "PTR_x"):
+ pattern = "PTR_x"
+ }
j := i
for ; j < len(s); j++ {
c := s[j]
-\t\t\t\tif (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') {
+\t\t\t\tif !strings.ContainsRune(chars, rune(c)) {
break
}
}
-\t\t\ts = s[0:i] + "PTR" + s[j:]
+\t\t\ts = s[0:i] + pattern + s[j:]
}
if s != tt.out {
if _, ok := tt.val.(string); ok {
コアとなるコードの解説
src/pkg/fmt/print.go
の変更
fmtPointer
関数は、reflect.Value
として渡されたポインタの値を処理し、指定された書式動詞(verb
)に従って文字列に変換します。
-
use0x64 := true
: この行で、use0x64
という新しいブーリアン変数が導入され、デフォルトでtrue
に初期化されます。これは、ポインタのデフォルトの書式設定(%p
や%v
)が引き続き0x
プレフィックス付きの16進数形式であることを示しています。 -
switch verb
文の変更:- 元のコードでは、
'p', 'v', 'b', 'd', 'o', 'x', 'X'
のすべての書式動詞に対して同じ処理(コメントアウトされた行)が行われていました。 - 変更後、
'p'
と'v'
のケースは独立し、use0x64
はtrue
のままです。 - 新たに
'b', 'd', 'o', 'x', 'X'
のケースが追加され、これらの書式動詞が使用された場合にuse0x64 = false
が設定されます。これにより、これらの整数基数指定の書式動詞では0x
プレフィックスが抑制されるようになります。
- 元のコードでは、
-
書式設定ロジックの条件分岐: ポインタの数値
u
が取得された後、実際の文字列変換ロジックが変更されました。if use0x64
:use0x64
がtrue
の場合(つまり、%p
または%v
が使用された場合)、p.fmt0x64(uint64(u), !p.fmt.sharp)
が呼び出されます。この関数は、0x
プレフィックスを付加した16進数形式で数値を整形します。else
:use0x64
がfalse
の場合(つまり、%b
,%d
,%o
,%x
,%X
のいずれかが使用された場合)、p.fmtUint64(uint64(u), verb, false)
が呼び出されます。この関数は、指定されたverb
(基数)に従って符号なし整数を整形しますが、0x
プレフィックスは付加しません。これにより、ポインタのメモリアドレスが純粋な数値として、指定された基数で出力されるようになります。
この変更により、fmt
パッケージはポインタの書式設定において、より細かく、かつ直感的な制御を提供できるようになりました。
src/pkg/fmt/fmt_test.go
の変更
テストファイルでは、新しいポインタの書式設定の挙動を検証するためのテストケースが追加され、既存のテストヘルパーロジックが改善されました。
-
fmttests
配列への追加:fmttests
は、fmt.Sprintf
のテストケースを定義する構造体のスライスです。{"%d", new(int), "PTR_d"}
{"%o", new(int), "PTR_o"}
{"%x", new(int), "PTR_x"}
これらの行は、new(int)
(任意のポインタを生成)をそれぞれの書式動詞でフォーマットした際の期待される出力パターンを定義しています。PTR_d
、PTR_o
、PTR_x
は、テスト実行時に実際のポインタアドレスの数値部分に置き換えられるプレースホルダーです。
-
TestSprintf
関数のPTR
置換ロジックの改善:TestSprintf
関数内には、ポインタのアドレスが実行環境によって異なるため、テストの比較を容易にするために実際のポインタアドレスを汎用的な文字列(PTR
)に置換するロジックがあります。このコミットでは、そのロジックが拡張されました。pattern
とchars
変数が導入されました。pattern
は置換後のプレースホルダー(PTR
,PTR_d
,PTR_o
,PTR_x
)を、chars
はそのプレースホルダーに対応する数値の有効文字セットを定義します。switch
文が追加され、tt.out
(期待される出力)がPTR_d
、PTR_o
、PTR_x
のいずれかで始まるかによって、pattern
とchars
が適切に設定されます。例えば、PTR_d
の場合はchars
が10進数文字(0-9
)のみに制限されます。if !strings.ContainsRune(chars, rune(c))
という条件が導入されました。これは、出力されたポインタアドレスの各文字c
が、その基数で有効な文字セットchars
に含まれているかをチェックします。これにより、テストはポインタアドレスの数値部分が正しい基数で出力されていることを検証できます。- 最後に、
s = s[0:i] + pattern + s[j:]
によって、実際のポインタアドレスの文字列が、対応するpattern
(PTR
,PTR_d
など)に置き換えられ、期待される出力と比較されます。
これらのテストの変更は、fmtPointer
関数の新しい挙動が正しく実装されていることを確認するための重要な役割を果たしています。
関連リンク
- Go Issue #3936: https://github.com/golang/go/issues/3936 (このコミットによって修正された問題)
- Go Change List 6441152: https://golang.org/cl/6441152 (このコミットのコードレビューページ)
参考にした情報源リンク
- Go言語
fmt
パッケージ公式ドキュメント: https://pkg.go.dev/fmt - Go言語
reflect
パッケージ公式ドキュメント: https://pkg.go.dev/reflect - Go言語におけるポインタの基本 (A Tour of Go - Pointers): https://go.dev/tour/moretypes/8