go-printf - 背景
歴史的経緯
printf の起源
printf は1970年代初頭、Ken Thompson によって UNIX オペレーティングシステムのために開発されました。最初は B 言語で実装され、その後 Dennis Ritchie によって C 言語に移植されました。
名前の由来は "print formatted" で、フォーマット文字列に従ってデータを整形して出力する機能を提供します。
フォーマット文字列の進化
1972: C言語の printf 誕生
1989: ANSI C で標準化
1999: C99 で %lld, %zu 追加
2011: C11 で %a (16進浮動小数点) 追加
各言語での printf 派生
| 言語 | 関数/メソッド | 特徴 |
|---|---|---|
| C | `printf()` | オリジナル |
| Python | `%` 演算子, `format()` | 型安全でない/型安全 |
| Java | `String.format()` | 型チェックなし |
| Rust | `format!()` | コンパイル時チェック |
| Go | `fmt.Printf()` | リフレクション使用 |
コンピュータサイエンス的な意味
フォーマット文字列の解析
printf のフォーマット文字列解析は、有限状態機械(FSM)で実装されます:
状態遷移図:
[通常] --'%'--> [開始] --フラグ--> [フラグ] --数字--> [幅]
| | |
v v v
[指定子] [指定子] [精度開始]
| | |
v v v
[出力] [出力] [精度]
|
v
[指定子]
|
v
[出力]
進数変換アルゴリズム
整数を任意の基数に変換するアルゴリズム:
function toBase(n, base):
if n == 0:
return "0"
digits = ""
while n > 0:
remainder = n % base
digits = digitChar(remainder) + digits
n = n / base
return digits
可変長引数の実装
C では可変長引数はスタックベースで実装されています:
// C の実装
void printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
// va_arg(ap, type) でスタックから取得
va_end(ap);
}
Go では interface{} のスライスとして実現されています:
func Printf(format string, args ...interface{}) {
// args は []interface{} として扱える
for _, arg := range args {
// 型スイッチで処理
switch v := arg.(type) {
case int:
// ...
}
}
}
実践での活用
ログ出力
log.Printf("[%s] %s: %v", timestamp, level, message)
デバッグ
fmt.Printf("DEBUG: x=%d, y=%d, result=%v\n", x, y, result)
文字列フォーマット
filename := fmt.Sprintf("%s_%04d.txt", prefix, index)
課題と実世界のギャップ
1. 型安全性
Go の標準 fmt パッケージはリフレクションを使用しているため、コンパイル時の型チェックがありません。Rust の format!() マクロはコンパイル時にチェックします。
2. パフォーマンス
リフレクションを使用する printf はパフォーマンスオーバーヘッドがあります。高性能が必要な場面では、直接的な文字列構築が推奨されます。
3. セキュリティ
フォーマット文字列攻撃は、ユーザー入力をフォーマット文字列として使用すると発生します:
// 危険(Cの場合)
printf(userInput); // userInput が "%s%s%s" なら脆弱性
// 安全
printf("%s", userInput);
Go は比較的安全ですが、情報漏洩の可能性は残ります。
関連する標準規格
- IEEE 754 - 浮動小数点数の表現
- ISO/IEC 9899 - C言語標準
- POSIX printf - UNIX互換仕様