C++提案: バイトを正確に8ビットへ
(open-std.org)- P3477R0は、C++のバイトサイズを
CHAR_BITという実装定義値に委ねるのではなく、正確に8ビットへ固定しようという標準変更案 - 現代のプラットフォームはすでに8ビットバイトを前提に収束しており、GCC・LLVM・MSVCも関連するデフォルト値やマクロを8にしている
- POSIXはPOSIX.1-2001から
CHAR_BIT == 8を要求しており、C++20とC23における2の補数整数表現採用の流れも同じ方向にある - 非8ビットバイト対応は、言語・ライブラリ・ツールチェーン全体に小さな例外を残し、実際の現代C++の利用と合わないエッジケースの負担を生むと見られている
- PDP-10や一部のDSPのような例外的アーキテクチャは存在するが、新しいC++標準がそのために複雑さを維持し続けるべきかが核心的な争点
P3477R0の変更目標
- C++はCの
CHAR_BITマクロを取り込み、現在この値はバイトあたりのビット数を表す実装定義値になっている - P3477R0は、C++標準がバイトを8ビットと正式に要求するよう変更することを提案している
- コンピューティング初期にはさまざまなバイトサイズを許容する柔軟性に意味があったが、現代のハードウェアはほぼすべて8ビットバイト前提へ収束したという判断がある
コンパイラとプラットフォームの現状
- 主要コンパイラはすでに8ビットバイトを基本的な現実として扱っている
- GCCの過去の対応例では、
dsp16xxは2004年に、1750aは2002年に削除された - Web検索では
BITS_PER_UNITが8でない一部のGCC外部ポートが見つかるが、現代C++との関連性は薄いと見られている
POSIXと整数表現の流れ
- POSIXはPOSIX.1-2001以来、次の条件を要求している
- バイトは正確に8ビット
CHAR_BITは8SCHAR_MAXは127、SCHAR_MINは-128、UCHAR_MAXは255
- POSIXは、
int8_t追加の結果として8ビットcharと2の補数演算を要求すると説明している - C++20はP0907r4以降、2の補数表現のみをサポートしており、C23も同じ方向に進んでいる
- 現在のPOSIX準拠OSの例として、AIX、HP-UX、INTEGRITY、macOS、OpenServer、UnixWare、VxWorks、vz/OSが挙げられている
非8ビットバイトが残すコスト
- 8ビットバイト向けソフトウェアと非8ビットバイト向けソフトウェアは相互互換ではなく、非8ビットバイト対象のC/C++コードは事実上、CおよびC++の互換性のない方言に近いと見なされている
- 非8ビットバイトアーキテクチャのサポートは、言語やライブラリのさまざまな部分に小さいが不要な複雑さを残す
- コンパイラやツールチェーンは、現代の利用実態を反映しないエッジケースを抱え続けなければならない
- 新しいプログラマは、C++のこうした特殊な性質によって混乱しやすい
- 一部の熟練プログラマは、存在しないプラットフォーム向けの「移植性」に時間を使ってしまうと見られている
例外的アーキテクチャと折衷案
- 非8ビットバイトのプロセッサがなお存在することは、この提案も認めている
- 核心的な問いは、それらのプロセッサが**現代C++**に関係するのか、そしてその利用者が新しいC++バージョンを使うのかどうかである
- 折衷案として
CHAR_BIT % 8 == 0を要求する案も示されているが、これは委員会がCHAR_BITは8ではないが8の倍数であるDSPやその他のプロセッサを引き続き支援すると決める場合にのみ意味がある - PDP-10は議論対象だが、PDP-11は8ビットバイトを使うものとして区別されている
- 一部のDSPは24ビットまたは32ビットワードを「バイト」のように扱っており、こうしたアーキテクチャは、ワードサイズが多様でバイト概念が標準化されていなかった時代には妥当だった
標準文言の変更方針
intro.memoryにおけるバイト定義を変更し、C++メモリモデルの基本的な記憶単位であるバイトが8ビットであることを明記しようとしているclimitsでは、CHAR_BITを8とする方向の文言変更を提案しているcstdintでは、バイトが8ビットになるため、int8_t、uint8_tなど幅指定整数型と関連マクロは任意ではなくなる_N_を使う型のうち、Nが8、16、32、64でないものは引き続き任意とするlocalization内のCHAR_BIT == 8に関する4つのmandates条項を削除する変更も含まれる
C標準との関係
- この提案は、C++が非8ビットバイトアーキテクチャと今後も関わり続けるべきかを検討している
- C委員会は、C言語について別の結論に至る可能性がある
- 両委員会が足並みをそろえるのが理想だが、この提案ではWG14とSG22の連絡グループがWG21に情報提供する形を想定している
1件のコメント
Hacker News のコメント
JF による「実際のコンピュータはみんなこう動いていると認めてもいいのでは?」シリーズには、すでに 符号付き整数は 2 の補数という回があった: "Signed Integers are Two’s Complement"
ただしそうなると、この Linux カーネルの古典的なコードは時代遅れになってしまうかもしれない: https://github.com/torvalds/linux/blob/master/include/math-emu/double.h#L29
1986年にインターンをしていたとき、10ビットバイトを使う BBN C/70 で C コードを書いたが、ひどい経験だった。そもそもそんなマシンが存在したこと自体が、悪い意味で宇宙的な事故だった
36ビットワードと 7ビットバイトを使っていて、バイトをワードに詰め込むとビットが余った。そこへ 8ビット形式のバイナリデータが入ったテープを読む仕事を任されたので、大混乱になった
ただし 10ビット C となると話は別だ
D は次のように定めたことで大きく前進した: バイトは 8ビット、short は 16ビット、int は 32ビット、long は 64ビット、演算は 2 の補数、浮動小数点は IEEE 浮動小数点
こうしたものを抽象化しようとして結局間違えることに費やしていた膨大な時間が節約され、何百万人もが安堵した。文字集合も EBCDIC や RADIX-50 ではなく Unicode だった
u8/i8、u16/i16、u32/i32、u64/i64のようにサイズが明示され、演算も明示的に選択する+のオーバーフローは不正な動作なので debug と releasesafe では停止し、+%は 2 の補数ラッピング、+|は飽和演算である。@addWithOverflow()は元の型とu1のタプルを返し、std.math.add()はオーバーフロー時にエラーを返す。f16、f32、f64、f80、f128もそれぞれ該当するビット長の IEEE 浮動小数点型である。バイト長がいくつかは重要ではなく、12ビットバイトのマシンならu12とi12を使えばよいu8,i32のような型名のほうが、あらゆる面ではるかに優れているunsignedの扱いは誤ったが、プリミティブ型のビット数の標準化はきちんと行ったbyte = 8 bits,short = 16,int = 32,long = 64,float = 32 bit IEEE,double = 64 bit IEEE今でも DSP を扱わなければならない人たちはいる: https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
個人的には「競合よりバイトあたりのビット数が 50% 多い!」という未実装の 12ビット・ファンタジーコンソールを冗談半分で文書化しているところで、「UTF-12」のような発明品も入れてある
何年も尋ねてきたが肯定的な答えは得られず、言及されたのは TI くらいだったので、更新草案に情報を追加した: https://isocpp.org/files/papers/D3477R1.html
CHAR_BIT=32のサポートを外しても、泣いて抗議したりはしないだろうC++ が何かを廃止したり単純化したりできるのか気になる
素朴な疑問で、詳しく追っているわけではない。
rand()は壊れていて直せないと聞いたが、最後に知っている限りでは、まだ廃止予定にさえなっていなかった。この提案は「文字どおり誰も遭遇していない問題への解法サポートすらなくせるのか?」という試金石のように見える現代の C と C++ はそれを捨て、2の補数を要求している。ここでは「as if」の区別も実質的には重要ではなく、
CHAR_BITにも同じように適用できるので、こうした変更の前例は明らかにあるrandも廃止予定にして代替も提供しているそのほかにも
p2809 Trivial infinite loops are not Undefined Behavior、p1152 Deprecating volatile、p0907 Signed Integers are Two's Complement、p2723 Zero-initialize objects of automatic storage duration、p2186 Removing Garbage Collection Supportがある。だから変えることは可能だ例外仕様も削除されたが、値型例外のために復活させたい人たちもいる。
auto_ptrも壊れた設計のせいで削除された。ただし単純化という面では、昔のやり方も依然として知っておく必要があるので、あまり改善にはなっていない互換性を壊さずに8ビットバイトを安定して指せる新しい C++ の記号が必要だ、という話だ。たとえば
unsigned byte8、2の補数のsigned byte8、符号の動作が定義されていないbyte8まで作れる。会計士向けに値の範囲を 0〜10、-10〜+10 に制限するunsigned decimal byte8とsigned decimal byte8、バイト単価まで気にする会計士向けに 0〜100、-100〜+100 のcentimal byte8、データベースのageフィールドにはだいたい十分な型、そしてもちろんfloat byte8も追加しよう、という冗談だrand()がなぜ壊れているのかわからない。ランダムっぽく見える値を生成し、それが目的だ暗号学的に安全な乱数を作らないのは当然で、ほかの言語の同等の関数も同じだ。高速に計算される、そこそこランダムな整数なら
rand()は十分うまく動く提案に関心を持ってくれてありがとう。これまでにもらったフィードバックをもとに更新版のドラフトを作った: https://isocpp.org/files/papers/D3477R1.html
特に「問題は、バイトが8ビットではないアーキテクチャがまだ存在するかどうかではない。存在する! 問題は、それらが現代 C++ を気にしているのか、そして現代 C++ がそれらを気にしているのかだ」という一文が印象的だ
この提案には両義的な感情がある。一方では明らかに正しく、
CHAR_BITが 8 でないことに意味のある用途はない他方では、コンピュータ内部についての個人的で過度に単純化されたモデルだけで、世界は筋が通り推論可能であるべきだという公正世界観に屈している感じもする。こうしたアプローチはかなり遠くまで連れて行ってくれるが、結局は行き止まりで、最後には何も知らないということ、そしてドキュメントが正しいという条件の下でだけ正しいプログラムを構成した、という形式的な論証が最善だと認めるしかない。これは大きな知的飛躍で、個人的には、それを認めるよう強制されずに長く過ごすほど、後で飛び越えるのが難しかった。それでも最近は初心者の間で物理電子工作プロジェクトが人気になっているようなので、「ドキュメントを読め」の代わりに「くそったれなデータシートを読め」が新しい標準になることを願っている
autoconfスクリプトを走らせるたびに、バイトのビット数を検査してconfig.hに保存しているのを目にする。まるで誰かがその値に応じて実際に振る舞いを変えるつもりでもあるかのように見える既存コード全体をコンパイル過程で検査し、このマクロがすでに使われているかを確認する方法が必要だ。こうした破壊的変更は言語を分断する危険もある。既存コードベースが
CHAR_BITマクロを使っているか、新しいコンパイラへ更新可能かをテストする難しさも明確ではない。どのライブラリが壊れたと見なされるのか、CHAR_BITを使ってコンパイルされた別のコードと相互作用する場合にも問題が起きるのか、といった疑問も浮かぶ。直感に反しているという点には同意するが、まず変換ツールを作り、極端なケースでも安全だと示したうえで移行するほうがよいと思う議論の余地がないのに、とてつもなく辛口な提案で気に入った
int8_t == char == 8 bitsを強制するのはまったく問題ないが、バイトは8ビットという誤解を広めることには確信が持てない8ビットバイトはオクテット(octet)と呼ぶ。同時に C++17 以降、
byteはすでにcharの「別名」のような存在でもある: https://en.cppreference.com/w/cpp/types/byteその後45年間、“byte” が別の意味で使われるのを見たことがないので、8ビットではない “byte” の定義があるなら出典が必要だ
これは記述的な主張ではなく、規範的な主張だ
int8 == signed charが嫌いだstd::cout << (int8_t)32 << std::endl;は当然 32 を出力するべきだC++とは関係ないけれど、6ビットバイトのレトロなマイクロコンピュータというアイデアはかなり気に入っている。24ビットがワードになる、という具合
マイクロコンピュータはたいてい少数のオブジェクトを扱い、ポインタより配列を好むので、メモリを節約できる。VGAは色ごとに6ビットだったし、6x4ビットの行列で読めるアルファベットを作れる。基本的なLISPやForth言語も6ビットのアルファベットに収められるし、もともとSystem/360は24ビットアドレスしか使っていなかった。独立してアドレス指定できる6ビット単位の12MiBメモリがあれば、誰にとっても十分なはず。足りなければ、FAT-12をFAT-24へ自然に拡張するか、64ビットポインタと同じくらい有用な48ビットポインタを使えばいい