Linux、6年・360件超のパッチを経て `strncpy` API を削除
(phoronix.com/news)- Linux 7.2 でカーネル内部の strncpy API の使用箇所がなくなり、以前から廃止予定だった文字列コピーインターフェースが最終的に削除された
strncpy()は指定したバイト数だけコピーするが、NUL 終端の挙動が直感的ではなく、カーネルでは長年にわたりバグの原因となっていた- 宛先バッファを不要に 0 で埋める性質は 性能問題 まで引き起こし、これを取り除くのに約 6 年と 362 件のコミットが必要だった
- 金曜日のマージでは、API 本体だけでなく最後に残っていた per-CPU のアーキテクチャ別実装 もあわせて削除された
- カーネルコードでは今後、用途に応じて
strscpy(),strscpy_pad(),strtomem_pad(),memcpy_and_pad(),memcpy()といった代替関数を選ぶ必要がある
Linux 7.2 で消えた strncpy
- Linux 7.2 は、カーネルで以前から 廃止予定 とされていた
strncpyAPI を最終的に削除した - 6 年にわたる整理作業の末、カーネル内部で
strncpyインターフェースを使うコードは完全になくなった - 今回の変更は単なる関数の置き換えではなく、古い文字列コピー慣行をカーネル全体から取り除く作業に近い
削除までにかかった作業規模
strncpyの削除には約 362 件のコミット が必要だった- 作業は、カーネル内部の
strncpy使用コードを段階的になくしていく形で進められた - Linux 7.2 でこの整理作業は完了に達した
strncpy がカーネルで問題だった理由
strncpyは Linux カーネル内で長年 継続的なバグ要因 とみなされていた- 特に問題だったのは次の 2 つの挙動
- NUL 終端 の意味と動作が直感的ではなく、利用者がミスしやすい
- 宛先バッファを重複して 0 埋めするため、不要な性能コストが発生する
実際の削除マージ
- 金曜日に行われたマージで
strncpyAPI が削除された - 同じマージで、最後に残っていた per-CPU のアーキテクチャ別
strncpy実装 もあわせて消えた
カーネルコードで使う代替 API
strncpyの代わりに、コピー先と終端条件に応じた関数を選ぶ必要があるstrscpy(): NUL 終端される宛先に使用strscpy_pad(): NUL 終端される宛先で 0 パディングが必要な場合に使用strtomem_pad(): NUL 終端されない固定幅フィールドに使用memcpy_and_pad(): 明示的なパディングを伴う制限付きコピーに使用memcpy(): 長さが分かっているメモリコピーに使用
1件のコメント
Hacker Newsのコメント
昔は、世界最高水準のC開発者である Linuxカーネル開発者 たちが stringbuffer や stringview 型も作れないとからかわれていたが、当時は今のようにこの話題について合意があった時代ではなかったので、ある程度は理解できる。
正しい方向性をすでに見ていたのは Dennis Ritchie で、1990年にC向けの fat pointer 型を提案していた。これがC99に入っていれば完璧な追加機能になっていたはずで、委員会が採用していれば世界はかなり違っていたかもしれない。
2007年には Walter Bright の「C's greatest mistake」という記事が出て、2度目のチャンスがあった。これは本質的には Ritchie と同じ発想である slice/stringview をより明確に説明していたが、それでもC11には入らなかった。C23まで来てもまだ存在せず、その代わりに _Generic と VLA を得たので、盛大に祝おうという感じだ。
検索していたら同じ話題の Reddit 投稿も見つけたが、自転車小屋論争が面白かった: https://www.reddit.com/r/C_Programming/comments/90uq7c/cs_bi...
Cの配列がポインタに崩壊する動作が、なぜそう設計されたのか気になる。Bのコードを最小限の変更でCにコンパイルできるようにするのが目的だったという説明があるが、Bでは配列宣言が実際にはポインタと配列を定義し、そのポインタが配列の最初の要素を指すよう初期化されていたらしい。
今のもっと大きな問題は、C標準ライブラリがいまだにK&R時代に縛られており、C99で追加された構造体引数や戻り値のような言語機能すら標準ライブラリAPIに反映されていない点だ。標準ライブラリにポインタ/サイズの組である 範囲構造体 と、それを使う新しい文字列関数や更新版の文字列関数があるだけでも、かなり改善できるはずだ。
Linuxカーネル内の strncpy は、直感に反するセマンティクス、NUL終端処理、宛先を不要に0で埋めることによる性能問題のせいで、何年もの間「しつこいバグの温床」だったという。
Cコードレビューを頼まれるたびに strncpy を探していたが、毎回そこでバグを見つけていた。
40年間ずっと気に障ってきたものがある。NUL終端文字列、そして今では入出力におけるUTF-8以外の文字列まで含まれる。
行末をLF、CR、CRLFで扱う慣習もそうだし、パイプやカンマでフィールドを区切る方式もそうだ。GS、FS、RSのような曖昧でないASCII文字を使っていれば、行末のエンコード/デコードは入出力の問題になり、HT/VT/CR/LF/FFは文字通り出力関連のコードとして残せたはずだ。
カンマ区切りデータで発生する厄介な エスケープ処理 の悩みが消えて、ずっと単純になった。
Unicode標準は、CR、LF、CRLFとこれらの文字だけでなく、垂直タブ と フォームフィード も行区切りとして扱うべきだとしている。
LF、CR、CRLFのような行末はOSの慣習でもあり、プログラミング言語が正しい行末を「推測」しようとしないほうがよい。これは解決する以上に問題を増やし、繰り返しになるが、たいていはWindows特有の問題なので、Microsoft が Windows を今世紀へ連れてくるべきだ。
最後に bash でCSVファイルを扱わなければならなかったときは、内部的に RS と FS に変換して処理した。
strncpy の代わりに Linuxカーネルのコードでは、NUL終端の宛先には strscpy()、0パディングが必要なNUL終端の宛先には strscpy_pad()、NUL終端でない固定幅フィールドには strtomem_pad()、明示的パディング付きの境界コピーには memcpy_and_pad()、長さが分かっているメモリコピーには memcpy() を使えという。
これは悪夢のようで、ここまで 複雑である必要があるのか わからない。
コードを読むとき、関数の選択だけで開発者の意図が明確に見えるほうがよいと思う。
こういう退屈な反復作業こそ、システムエンジニアリングの本当の仕事 が行われる場所だ。
Linuxカーネルを全工程を通して実運用可能な状態に保ちながら、より信頼性の高いものにしていくこうした大規模インフラプロジェクトは、数か月ではなく数十年単位で動く。
ただ、その速度で長期的に意味のある進歩を生み出せるのかはよくわからない。不満というより、中核インフラのパラドックス に近い。
これはすごいし、謙虚な気持ちにもなる仕事だ。これほど多くの人が貢献したことに驚かされる
「すばらしい新機能」は功績として認められやすいが、カーネルのような根本的な対象では、悪い機能を取り除くことのほうがむしろ重要なのかもしれない
50年後、人々がソースコードの読み方を忘れ、Claude/Codexの残骸が静かに積み上がって地球のエネルギーの大半を燃やす時代が来たら、こうした仕事は「建国時代」の伝説のように語られる気がする
Unix epoch が何かを知っている唯一の人物でもある
ヌル終端文字列 は、コンピューティング史上最大の失敗だと思う。Pascal式文字列のほうがずっと安全だった
依然として0で終わる文字配列を指すポインタだが、ポインタが指す先頭バイトの直前に長さフィールドがある。埋め込みNUL文字がないという前提ではC文字列とも互換性があり、BSTR型の関数は長さの値を利用できる
しばらくの間は16ビットですら大きすぎると感じられたかもしれないし、今では32ビットが小さすぎるように見えるかもしれない。「強い型付け」の言語であるはずのCは、実際に重要だった場所ではかなり緩い
Pascal関連のコードは30年以上書いていないが、当時でも文字列システムは使いづらすぎると思っていた、というかすかな記憶がある
文字列型 が1つないだけで生じる苦労と無駄な作業が多すぎる
strncpy の使用箇所を書き換えるのに、何がそんなに難しくて 6年 もかかったのか気になる
使用箇所がそれだけ広範だったのか、それとも同じファイルに手を入れる機会があるときだけ直していく長期作業だったのか、あるいは別の難しさがあったのか知りたい
Win32アプリで 空白パディング文字列 を使うコードを扱ったことがある。宛先文字列は空白でパディングされるが、最後のバイトにはそれでもヌル文字があった
長さやコピーのような処理には、専用の文字列関数版を使わなければならなかった。なぜそうだったのかは分からないが、コードベースがあまりに古いので、Pascalの構造体の挙動に由来していたのかもしれない