📝

char の符号が処理系定義な理由

2024/05/29に公開

プログラミング言語 C および C++ において型 char が符号付きか符号無しかは処理系定義とされている。 また、 intsigned int と同じ意味なのに対して charsigned int とも unsigned int とも異なる独立した型であるという変則的な扱いをされている。

どうしてこうなっているのかについてはっきりした資料を見つけられていないのだがおそらく文字コードと汎整数拡張 (integral promotion) に原因がある。

まず一番最初の前提として C の言語仕様を取りまとめようとしたときにはすでに微妙に違う挙動をする処理系がいくつも存在しており、主要な処理系の挙動を追認した部分が多かったということだ。 規格を確立してから実装が始まったのではなく、処理系が出来てからそれを他の環境に移植したり拡張したりということがあった後で規格化が試みられたのである。 矛盾するところを受け入れるために言語仕様としてはどちらもアリということにしたというような部分が数多くあり、何十年もたった今でも統一していない。

文字コードの話をする。 文字はそれをビットパターンに対応付けて表現する仕組みがあり、現代の文字コードの源流にはアスキーコードがある。 C の入門書でもアスキーコードを前提としたような説明をしているものはよくある。

しかし、 C の言語仕様では文字コードとしてアスキーコードを使うことを保証していない。 言語仕様では最低限度として扱えなければならない文字として実行基本文字集合を定めている他、 '0''9' に対応する文字コードが連続するということ、実行基本文字集合を char に格納した場合にその値は非負であることという定めがある程度だ。 実行環境の都合でアスキーコード以外の文字コードを採用している場合が想定されている。

さて、このとき重要なのは「非負」という制約である。 現代ではアスキーコードかその発展形の文字コードが主流なので char が符号付きであっても実行基本文字集合は非負として表現可能なのだが、かつてはアスキーコードの他の文字コード系統として EBCDIC もそれなりに強い存在感があったらしいのだ。 日本においても行政や銀行といったところで使われていたいわゆるメインフレームでは EBCDIC の採用はかなり多かったらしい。 符号付きの char で EBCDIC を格納しようとすると非負という制約を満たせないので EBCDIC を採用しているシステムでは char は必然的に符号無しにならざるを得ない。

文字コードが非負であることという制約をつける必要があったのは、 char は文字型であると同時に整数型でもあるという点だと思う。 C の整数は int を中心にした仕組みになっていて様々な場面で「int より小さい大きさの整数型は int に暗黙に型変換してから処理する」というルールがある。 char 型の負数を (符号付き整数である) int に拡張すると符号拡張によってビットパターンが変わってしまい扱いが煩雑になる。

char の規定に関してはいくつかの事情が絡み合っているので分かり難いが今では (ほとんど) 滅びた他の規格との関連で必要だった規定なのである。 原因が消えたからといって今更に統一するのもそれはそれで混乱の原因になるであろうし、歴史が長いとそういうこともある。

Discussion