PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

C言語はなぜ「未定義動作だらけ」なのか――記事が突きつける、ちょっと笑えない現実

キーポイント

本文

今回紹介する記事は、タイトルからしてかなり挑発的です。
​「Everything in C is undefined behavior」​――直訳すれば「Cのすべては未定義動作だ」。もちろん厳密には言いすぎですが、著者はあえて極端な表現で、C/C++の怖さを読者に叩きつけています。

率直に言うと、これはかなり面白い記事です。
なぜなら、単に「Cは危ないよね」で終わらず、​**“どこがどう危ないのか”** を、具体例を積み上げながら見せてくるからです。しかもその例が、いかにもありがちなものばかり。読んでいて「え、それもダメなの?」と何度もなるはずです。


著者の主張はかなりシビア

記事の出発点はシンプルです。

著者は30年近くC/C++を書いてきた人らしく、単なる外野の批判ではありません。
そのうえで「正しいCやC++を書くのは、もはや無理ではないか」とかなり強い口調で語っています。

ここは少し意見が分かれるところだと思います。
私は「さすがに 誰も 正しいコードを書けない」は言い過ぎだと思う一方で、​**“正しく書ける人がいても、間違えやすさそのものが言語に深く埋まっている”** という指摘はかなり本質的だと思います。


まず大前提: 未定義動作とは何か

未定義動作、いわゆる UB(undefined behavior)​ は、簡単に言うと、

そのコードが動いたときに、何が起きてもC/C++の仕様上は保証されない

という状態です。

ここが重要です。
「たぶん落ちる」とか「たまに変な値になる」ではありません。
仕様として“何が起きてもよい”ことになっている のです。

なので、コンパイラは「そんなことは起きない前提」で最適化できます。
著者が強調しているのは、UBは「コンパイラが意地悪をしている」のではなく、​そもそもコンパイラに“この場合どうするか”を義務づけていないという点です。

個人的には、ここがCの難しさの核心だと思います。
人間は「意図」を読めますが、コンパイラは「仕様」しか読めない。
このギャップが、Cではかなり致命的なんですよね。


「最適化してないから大丈夫」は幻想

よくある誤解として、
「最適化を切ればUBは大したことない」という考えがあります。

記事はこれをはっきり否定します。
UBは「最適化が暴走する話」ではなく、​**“そのコードがそもそも言語仕様の外に出ている”** という話だからです。

つまり、コンパイラは

という立場になります。

これ、地味に怖いです。
今日たまたま動いていても、コンパイラの版が変わっただけで挙動が崩れる可能性がある。
「現時点で動く」は、「安全」ではないんですよね。


記事が挙げる「ありがちなUB」の数々

1. アラインメントされていないポインタの参照

たとえば、こんな関数です。

int foo(const int *p) {
    return *p;
}

見た目は普通です。
でも pint に必要な位置に揃っていないと、これがUBになります。
アラインメント とは、簡単に言えば「データを置くべき位置のルール」です。CPUによっては、このルールがかなり厳しいです。

記事では、

という具合に、アーキテクチャ差を挙げています。

ここがCの嫌なところで、​**“自分のPCで動く”が何の保証にもならない**。
しかも未来のCPUでは、また違う扱いになるかもしれない。
著者の言う通り、これは本当に「電話ゲーム」みたいなものです。仕様 → コンパイラ → CPU → 実機、のどこかで意図が崩れる。


2. ポインタに変換した時点でアウトなことがある

これもかなり嫌なポイントです。

const int *magic_intp = (const int *)bytes; // UB!

多くの人は「まだ参照してないからセーフでは?」と思うかもしれません。
でも記事の主張では、​キャストした時点で問題になることがある のです。

これは本当に直感に反します。
「読み出してから壊れる」のではなく、「そういう型のポインタだとみなした瞬間にアウト」になりうる。
Cはこういう“先回りの地雷”が多いので、慣れていても油断できません。


3. isxdigit()char をそのまま渡す問題

bool bar(char ch) {
    return isxdigit(ch);
}

ぱっと見、何が悪いのか分かりにくいです。
でも char が signed の環境では、値によっては負数になりえます。
isxdigit() のような文字判定関数は、基本的に int を受け取る 前提で、しかも範囲が想定されています。

もし不正な値が来たら、内部で配列アクセスのような処理が起きて、想定外の場所を読んでしまうかもしれない。
最悪、メモリ上の別の領域を触るかもしれない。

ここで面白いのは、文字判定という「すごく日常的な処理」ですら危ないこと。
Cは本当に、平和そうに見える場所にトラップを仕込んでくるな、と思います。


4. float を int に変換するのも意外と危ない

記事では、秒をミリ秒に変換する例が出ます。

int tmp = (int)(seconds * 1000.0);

これ、実際にかなりのコードで見ます。
でも問題は、​変換結果が int の範囲に収まらない場合はUB だということです。
さらに、浮動小数点が非有限値(NaNや∞)でも危険です。

「じゃあ範囲チェックすればいいじゃん」と思うのですが、そこにも罠がある。
int を float に変換する時点で丸めが入るかもしれず、比較自体が怪しくなる。
著者はそこを丁寧に追いかけていて、読んでいると「うわ、面倒くさすぎる」となるはずです。

私もここはかなり印象的でした。
“ただの型変換” が、こんなに神経を使う操作になる言語は珍しい と思います。
もちろん厳密な世界では意味があるのですが、一般的な開発で毎回これを気にするのは、かなりしんどいです。


5. address zero にオブジェクトを置く問題

ゼロ番地にオブジェクトを置く話も出てきます。
これはOSカーネルや組み込みで出る話で、一般アプリではあまり見ません。

でも著者が言いたいのは、「NULL は単なるアドレス0ではない」ということです。
Cの世界では、NULL は**“ヌルポインタ定数”** であって、必ずしも物理アドレス0を意味しません。

しかも、null pointer を参照するのはもちろんUBです。
そして、memset(&ptr, 0, sizeof(ptr)) でポインタが必ずNULLになるとも限らない。

ここは、Cの“歴史の重さ”を感じるところです。
昔の機械ではNULLが0じゃなかったこともある。
つまり、私たちが当たり前だと思っている感覚も、言語仕様の上では保証されていないんですね。


6. 可変長引数は型がずれると即アウト

printfexecl のような variable arguments は特に危険です。

printf("%ld\n", blah);  // WRONG

uint64_t%ld を使うとダメ。
正しくは PRIu64 のようなマクロを使うべきです。

これ、地味に面倒です。
でも、可変長引数は「コンパイラが型を十分に検査しづらい」ので、ズレるとかなり危険。
“見た目は似ているのに、型が1個ずれただけで崩壊する” のが、Cの怖さです。

個人的には、ここは「Cの美しさ」ではなく「Cの不親切さ」が最も現れる場面のひとつだと思います。


7. ゼロ除算はもちろんUB

これはさすがに有名です。
でも記事は、ここにもセキュリティ上の意味があると指摘します。

割る側の値が外部入力なら、攻撃につながることもある。
つまりUBは、単なる「バグ」ではなく、​脆弱性の入口 になりうるわけです。

この視点は大事です。
未定義動作は「ちょっと危ない」ではなく、​安全保障の問題 でもある。
最近のソフトウェア開発で、C/C++が厳しく見られる理由のひとつはここにあります。


著者が言いたい本当のこと

この文章は、単に「Cは危険」と騒ぎたい記事ではないと思います。
むしろ本質は、

という問題提起ではないでしょうか。

ここはかなり重要です。
「注意して書けばいい」で片づけるのは簡単ですが、記事を読むとそれがいかに無理筋かが見えてきます。
もちろん熟練者はかなり避けられる。でも、​完全にゼロにするのは相当に難しい
著者の悲観は強めですが、現場感としては分からなくもないです。


じゃあC/C++はもう終わりなのか?

そこまでは言いません。
実際、C/C++は今も大量の基盤ソフトウェアを支えています。
OS、ブラウザ、組み込み、ゲームエンジン、高性能処理……どれも簡単には置き換えられません。

ただし、この記事を読むと、
​「C/C++を使うなら、危険を理解した上で、相当意識的に設計しないといけない」​
という現実はかなり強く感じます。

私の感想としては、Cは今でも強力だけれど、もはや「気軽に使える便利な言語」ではないです。
むしろ、​高い性能と引き換えに、仕様の地雷原を歩く言語 だと思ったほうが近い。


ひとつだけ補足: この記事は少し誇張もある

タイトル通り、記事はかなり煽り気味です。
なので「Cの全部がUB」というのはもちろん比喩です。
実際には正しく書ける部分もあるし、厳密に運用すれば安全性をかなり上げることもできます。

でも、誇張が効いているぶん、伝わるものもあります。
“自分のコードは大丈夫” という油断を壊す には、これくらい強い言い方のほうが効くのかもしれません。


まとめ

この元記事は、C/C++の未定義動作を「よくあるミス」ではなく、​言語の根っこにある構造的な危うさとして描いています。
そしてその描き方が、かなり鋭いです。

技術的に正しいだけでなく、読み物としても面白い。
とくに、
「動くじゃん」
「最適化切れば平気でしょ」
「このくらい大丈夫でしょ」
という“雑な安心”を次々に崩していく構成が見事です。

C/C++を使う人なら、少なくとも一度は読んでおきたいタイプの記事だと思います。
そして、Cをあまり知らない人にとっても、​低レベル言語の世界では「常識」がいかに脆いか を知る入門としてかなり良い素材ではないでしょうか。


参考: Everything in C is undefined behavior

同じ著者の記事