1 ポイント 投稿者 GN⁺ 2024-10-19 | 1件のコメント | WhatsAppで共有
  • 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はデフォルト値を8としており、アップストリームターゲットでこのデフォルトを変更する例はない
    • LLVM__CHAR_BIT__8に設定している
    • MSVCCHAR_BIT8と定義している
  • GCCの過去の対応例では、dsp16xxは2004年に、1750aは2002年に削除された
  • Web検索ではBITS_PER_UNITが8でない一部のGCC外部ポートが見つかるが、現代C++との関連性は薄いと見られている

POSIXと整数表現の流れ

  • POSIXはPOSIX.1-2001以来、次の条件を要求している
    • バイトは正確に8ビット
    • CHAR_BIT8
    • SCHAR_MAX127SCHAR_MIN-128UCHAR_MAX255
  • 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_BIT8とする方向の文言変更を提案している
  • cstdintでは、バイトが8ビットになるため、int8_tuint8_tなど幅指定整数型と関連マクロは任意ではなくなる
  • _N_を使う型のうち、N8163264でないものは引き続き任意とする
  • localization内のCHAR_BIT == 8に関する4つのmandates条項を削除する変更も含まれる

C標準との関係

  • この提案は、C++が非8ビットバイトアーキテクチャと今後も関わり続けるべきかを検討している
  • C委員会は、C言語について別の結論に至る可能性がある
  • 両委員会が足並みをそろえるのが理想だが、この提案ではWG14とSG22の連絡グループがWG21に情報提供する形を想定している

1件のコメント

 
GN⁺ 2024-10-19
Hacker News のコメント
  • JF による「実際のコンピュータはみんなこう動いていると認めてもいいのでは?」シリーズには、すでに 符号付き整数は 2 の補数という回があった: "Signed Integers are Two’s Complement"

  • 1986年にインターンをしていたとき、10ビットバイトを使う BBN C/70 で C コードを書いたが、ひどい経験だった。そもそもそんなマシンが存在したこと自体が、悪い意味で宇宙的な事故だった

    • DECSYSTEM-20 でコードを書いたことがあるが、C コンパイラは公式サポートではなかった
      36ビットワード7ビットバイトを使っていて、バイトをワードに詰め込むとビットが余った。そこへ 8ビット形式のバイナリデータが入ったテープを読む仕事を任されたので、大混乱になった
    • Intel Intellivision CPU をプログラミングしたが、10ビット declを使う奇妙なマシンで、C を動かせるほど強力ではなかった
    • 9ビットバイト81ビット命令を使うマシン、そして 6ビットバイトを使うマシンで働いたことがあるが、どちらにも C コンパイラはなかった
    • 最近の FPGA では 10ビット演算は実際珍しくなく、比較的現代的な製品にも使われている
      ただし 10ビット C となると話は別だ
  • D は次のように定めたことで大きく前進した: バイトは 8ビット、short は 16ビット、int は 32ビット、long は 64ビット、演算は 2 の補数、浮動小数点は IEEE 浮動小数点
    こうしたものを抽象化しようとして結局間違えることに費やしていた膨大な時間が節約され、何百万人もが安堵した。文字集合も EBCDIC や RADIX-50 ではなく Unicode だった

    • Zig はさらに優れている: u8/i8u16/i16u32/i32u64/i64 のようにサイズが明示され、演算も明示的に選択する
      + のオーバーフローは不正な動作なので debug と releasesafe では停止し、+%2 の補数ラッピング+| は飽和演算である。@addWithOverflow() は元の型と u1 のタプルを返し、std.math.add() はオーバーフロー時にエラーを返す。f16f32f64f80f128 もそれぞれ該当するビット長の IEEE 浮動小数点型である。バイト長がいくつかは重要ではなく、12ビットバイトのマシンなら u12i12 を使えばよい
    • D が大きく前進したというのは誇張だ。明示的なサイズ名である u8, i32 のような型名のほうが、あらゆる面ではるかに優れている
    • 「バイトは 8ビット」なら、ビットはどれくらい大きいのか?
    • D 言語の作者である Bright 氏が自分で言うには、少し自画自賛ではないかと思う :)
    • Java もこの部分は正しくやった。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」のような発明品も入れてある

    • まだ関係のある対象が何で、彼らが現代 C++ を狙っているのか、あるいはその予定があるのかを確認しようとしている
      何年も尋ねてきたが肯定的な答えは得られず、言及されたのは TI くらいだったので、更新草案に情報を追加した: https://isocpp.org/files/papers/D3477R1.html
    • 単に C++23 以下を対象にすればいいのではないかと思う。SHARC を何台か持っているが、委員会が C++30 のようなバージョンで CHAR_BIT=32 のサポートを外しても、泣いて抗議したりはしないだろう
    • PDP-8 は 12ビットバイトを使っていなかったか?
  • C++ が何かを廃止したり単純化したりできるのか気になる
    素朴な疑問で、詳しく追っているわけではない。rand() は壊れていて直せないと聞いたが、最後に知っている限りでは、まだ廃止予定にさえなっていなかった。この提案は「文字どおり誰も遭遇していない問題への解法サポートすらなくせるのか?」という試金石のように見える

    • 符号付き整数は必ずしも2の補数である必要はなく、符号・絶対値、1の補数、2の補数という3つの表現がすべて有効だった
      現代の C と C++ はそれを捨て、2の補数を要求している。ここでは「as if」の区別も実質的には重要ではなく、CHAR_BIT にも同じように適用できるので、こうした変更の前例は明らかにある
    • すでにトライグラフ(trigraph)を削除し、rand も廃止予定にして代替も提供している
      そのほかにも p2809 Trivial infinite loops are not Undefined Behaviorp1152 Deprecating volatilep0907 Signed Integers are Two's Complementp2723 Zero-initialize objects of automatic storage durationp2186 Removing Garbage Collection Support がある。だから変えることは可能だ
    • C++11 の GC API は C++23 で削除されたし、GC をサポートする主要な変種である Unreal C++ と C++/CLI の要求を考慮して設計されていなかったという点で理解できる
      例外仕様も削除されたが、値型例外のために復活させたい人たちもいる。auto_ptr も壊れた設計のせいで削除された。ただし単純化という面では、昔のやり方も依然として知っておく必要があるので、あまり改善にはなっていない
    • 完璧を壊すのではなく、さらに多くの完璧を積み重ねるべきだという風刺のように見える
      互換性を壊さずに8ビットバイトを安定して指せる新しい C++ の記号が必要だ、という話だ。たとえば unsigned byte8、2の補数の signed byte8、符号の動作が定義されていない byte8 まで作れる。会計士向けに値の範囲を 0〜10、-10〜+10 に制限する unsigned decimal byte8signed 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 に保存しているのを目にする。まるで誰かがその値に応じて実際に振る舞いを変えるつもりでもあるかのように見える
    • 広く使われる言語なら、結局は COBOL 問題にぶつかる。ほとんどの場合は問題ないが、更新を強制されたあるシステムで突然、交通管制システムが止まったり飛行機が墜落したりするかもしれない
      既存コード全体をコンパイル過程で検査し、このマクロがすでに使われているかを確認する方法が必要だ。こうした破壊的変更は言語を分断する危険もある。既存コードベースが 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ビット量として定義されていた
      その後45年間、“byte” が別の意味で使われるのを見たことがないので、8ビットではない “byte” の定義があるなら出典が必要だ
    • ネットワーキングの RFC は最初からずっと octet という表現を使ってきた
    • いや、バイトは8ビットだ
      これは記述的な主張ではなく、規範的な主張
    • 個人的には 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ビットポインタを使えばいい

    • あるいは、8ビットバイト3バイトワードを使えばいい。それでも24ビットだ