C++-Vorschlag: Ein Byte ist genau 8 Bit
(open-std.org)- P3477R0 ist ein Vorschlag zur Änderung des Standards, der die Byte-Größe in C++ nicht länger dem implementierungsdefinierten Wert
CHAR_BITüberlassen, sondern genau auf 8 Bit festschreiben will - Moderne Plattformen haben sich bereits auf die Annahme von 8-Bit-Bytes eingependelt, und auch GCC, LLVM und MSVC setzen entsprechende Standardwerte oder Makros auf 8
- POSIX verlangt seit POSIX.1-2001
CHAR_BIT == 8, und auch die Einführung der Zweierkomplement-Darstellung für Ganzzahlen in C++20 und C23 weist in dieselbe Richtung - Die Unterstützung von Bytes mit nicht 8 Bit hinterlässt kleine Ausnahmen in Sprache, Bibliotheken und Toolchains und erzeugt damit die Last eines Edge Case, der nicht zur realen modernen C++-Nutzung passt
- Ausnahmen wie PDP-10 oder bestimmte DSP-Architekturen existieren zwar, doch die Kernfrage ist, ob neue C++-Standards für diese Zielsysteme weiterhin Komplexität mitschleppen sollten
Ziel der Änderungen in P3477R0
- C++ übernimmt das Makro
CHAR_BITaus C; derzeit ist dieser Wert ein implementierungsdefinierter Wert, der die Anzahl der Bits pro Byte angibt - P3477R0 schlägt vor, den C++-Standard so zu ändern, dass ein Byte offiziell 8 Bit haben muss
- Dahinter steht die Einschätzung, dass die frühere Flexibilität für verschiedene Byte-Größen in den Anfangszeiten des Computings sinnvoll war, moderne Hardware heute aber fast vollständig auf 8-Bit-Bytes konvergiert ist
Aktueller Stand bei Compilern und Plattformen
- Führende Compiler behandeln 8-Bit-Bytes bereits als praktische Realität
- Unter den historischen GCC-Unterstützungsfällen wurde
dsp16xx2004 entfernt,1750abereits 2002 - Bei Websuchen finden sich zwar einige externe GCC-Ports mit
BITS_PER_UNITungleich 8, diese scheinen jedoch keinen Bezug zu modernem C++ zu haben
Entwicklung bei POSIX und Ganzzahldarstellungen
- POSIX verlangt seit POSIX.1-2001 folgende Bedingungen
- Ein Byte hat genau 8 Bit
CHAR_BITist8SCHAR_MAXist127,SCHAR_MINist-128,UCHAR_MAXist255
- POSIX erläutert, dass daraus infolge der Einführung von
int8_tein 8-Bit-charund Zweierkomplement-Arithmetik erforderlich werden - C++20 unterstützt seit P0907r4 nur noch Zweierkomplement-Speicherrepräsentationen, und C23 folgt derselben Richtung
- Als Beispiele aktuell POSIX-konformer Betriebssysteme werden AIX, HP-UX, INTEGRITY, macOS, OpenServer, UnixWare, VxWorks und vz/OS aufgeführt
Welche Kosten Nicht-8-Bit-Bytes verursachen
- Software für 8-Bit-Bytes und Software für Nicht-8-Bit-Bytes sind nicht miteinander kompatibel; C/C++-Code für Nicht-8-Bit-Byte-Ziele kommt damit praktisch einem inkompatiblen Dialekt von C und C++ gleich
- Die Unterstützung von Architekturen mit Nicht-8-Bit-Bytes hinterlässt an vielen Stellen in Sprache und Bibliotheken kleine, aber unnötige Komplexität
- Compiler und Toolchains müssen weiterhin einen Edge Case mittragen, der die moderne Nutzung nicht widerspiegelt
- Neue Programmierer können durch diese exotische Eigenschaft von C++ leicht verwirrt werden
- Manche erfahrene Programmierer investieren nach Ansicht des Vorschlags Zeit in „Portabilität“ für Plattformen, die in der Praxis nicht existieren
Ausnahme-Architekturen und Kompromissvorschläge
- Der Vorschlag erkennt an, dass es weiterhin Prozessoren mit Nicht-8-Bit-Bytes gibt
- Die Kernfrage lautet, ob solche Prozessoren für modernes C++ noch relevant sind und ob deren Nutzer überhaupt neue C++-Versionen einsetzen würden
- Als Kompromiss wird auch die Forderung
CHAR_BIT % 8 == 0genannt; sinnvoll wäre das allerdings nur, wenn das Komitee weiterhin DSPs oder andere Prozessoren unterstützen will, bei denenCHAR_BITzwar nicht 8 ist, aber ein Vielfaches davon - PDP-10 wird als Diskussionsfall genannt, während PDP-11 ausdrücklich als System mit 8-Bit-Bytes abgegrenzt wird
- Manche DSPs behandeln 24-Bit- oder 32-Bit-Wörter wie „Bytes“; solche Architekturen waren in einer Zeit plausibel, in der Wortgrößen stark variierten und das Byte-Konzept noch nicht standardisiert war
Richtung der Änderungen am Standardtext
- In
intro.memorysoll die Byte-Definition so geändert werden, dass das Byte als grundlegende Speichereinheit des C++-Speichermodells ausdrücklich 8 Bit umfasst - Für
climitswird eine Formulierung vorgeschlagen, nach derCHAR_BITden Wert8hat - In
cstdintwären wegen der 8-Bit-Bytes Integer-Typen mit festgelegter Breite wieint8_t,uint8_tsowie die dazugehörigen Makros nicht länger optional - Typen mit
_N_, bei denenNnicht8,16,32oder64ist, blieben weiterhin optional - Dazu gehört auch die Streichung von vier Mandates-Klauseln in
localization, die sich aufCHAR_BIT == 8beziehen
Verhältnis zum C-Standard
- Der Vorschlag prüft, ob C++ weiterhin an Architekturen mit Nicht-8-Bit-Bytes festhalten sollte
- Das C-Komitee könnte für die Sprache C zu einem anderen Ergebnis kommen
- Eine Abstimmung beider Komitees wäre ideal, doch der Vorschlag sieht hier vor allem vor, dass die Liaison-Gruppe WG14 und SG22 WG21 mit Informationen versorgt
1 Kommentare
Meinungen auf Hacker News
In JFs Reihe „Können wir nicht anerkennen, dass echte Computer alle so funktionieren?“ gab es bereits die Folge vorzeichenbehaftete Ganzzahlen sind Zweierkomplement: „Signed Integers are Two’s Complement“
Allerdings könnte dadurch dieser klassische Linux-Kernel-Code veralten: https://github.com/torvalds/linux/blob/master/include/math-emu/double.h#L29
Als Praktikant schrieb ich 1986 C-Code auf einer BBN C/70 mit 10-Bit-Bytes; das war eine schreckliche Erfahrung, und schon die Existenz solcher Maschinen war im negativen Sinne ein kosmischer Unfall
Es nutzte 36-Bit-Wörter und 7-Bit-Bytes, und wenn man Bytes in ein Wort packte, blieben Bits übrig. Dann bekam ich auch noch die Aufgabe, Bänder mit Binärdaten im 8-Bit-Format zu lesen – das war ein Chaos
Aber 10-Bit-C ist eine andere Geschichte
D machte einen großen Schritt nach vorn, indem es festlegte: Ein Byte hat 8 Bit, short 16 Bit, int 32 Bit, long 64 Bit, Arithmetik ist Zweierkomplement, Fließkomma ist IEEE-Fließkomma
Das sparte enorm viel Zeit, die man sonst damit verbracht hätte, solche Dinge zu abstrahieren und am Ende doch falsch zu liegen, und Millionen Menschen waren erleichtert. Auch der Zeichensatz war Unicode, nicht EBCDIC oder RADIX-50
u8/i8,u16/i16,u32/i32,u64/i64, und auch die Arithmetik wird ausdrücklich gewähltÜberlauf bei
+ist fehlerhaftes Verhalten und führt in Debug und releasesafe zum Abbruch,+%ist Zweierkomplement-Wrapping,+|ist saturierende Arithmetik.@addWithOverflow()liefert ein Tupel aus dem ursprünglichen Typ undu1, undstd.math.add()gibt bei Überlauf einen Fehler zurück.f16,f32,f64,f80,f128sind jeweils IEEE-Fließkommatypen der entsprechenden Bitlänge. Wie lang ein Byte ist, spielt keine Rolle; auf einer Maschine mit 12-Bit-Bytes würde man ebenu12undi12verwendenu8,i32sind in jeder Hinsicht viel besserunsignedwurde falsch behandelt, aber die Standardisierung der Bitbreiten primitiver Typen wurde ordentlich gelöstbyte = 8 bits,short = 16,int = 32,long = 64,float = 32 bit IEEE,double = 64 bit IEEEEs gibt immer noch Leute, die mit DSPs arbeiten müssen: https://thephd.dev/conformance-should-mean-something-fputc-and-freestanding#we-cannot-program-on--vibes-
Persönlich dokumentiere ich zum Spaß eine nicht implementierte 12-Bit-Fantasy-Konsole mit „50 % mehr Bits pro Byte als die Konkurrenz!“ und habe auch Erfindungen wie „UTF-12“ eingebaut
Ich habe jahrelang gefragt, aber keine positive Antwort erhalten; genannt wurde im Grunde nur TI, daher habe ich dem aktualisierten Entwurf Informationen hinzugefügt: https://isocpp.org/files/papers/D3477R1.html
CHAR_BIT=32streicht, werde ich nicht weinend protestierenIch frage mich, ob C++ überhaupt etwas abschaffen oder vereinfachen kann.
Ehrliche Frage, ich habe das nicht im Detail verfolgt. Ich habe gehört,
rand()sei kaputt und nicht reparierbar; nach meinem letzten Stand war es aber noch nicht einmal zur Abschaffung vorgesehen. Dieser Vorschlag wirkt wie ein Test nach dem Motto: „Können wir auch die Unterstützung für Lösungen zu Problemen entfernen, die buchstäblich niemand hat?“Modernes C und C++ haben das aufgegeben und verlangen Zweierkomplement. Hier ist auch die „as if“-Unterscheidung praktisch nicht relevant, und auf
CHAR_BITlässt sich das auf dieselbe Weise anwenden; es gibt also eindeutig einen Präzedenzfall für solche Änderungen.randwurde ebenfalls zur Deprecation vorgesehen, inklusive Alternativen.Außerdem gibt es
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 durationundp2186 Removing Garbage Collection Support. Änderungen sind also möglich.Exception Specifications wurden ebenfalls entfernt, auch wenn manche sie für Value-Type-Exceptions gern zurückhaben möchten.
auto_ptrwurde wegen seines kaputten Designs ebenfalls entfernt. Unter dem Aspekt der Vereinfachung bringt das allerdings nicht viel, weil man die alte Art trotzdem noch kennen muss.Also etwa: Man brauche ein neues C++-Symbol, das zuverlässig auf ein 8-Bit-Byte verweist, ohne Kompatibilität zu brechen. Zum Beispiel könnte man
unsigned byte8, ein Zweierkomplement-signed byte8und einbyte8mit undefiniertem Vorzeichenverhalten einführen. Für Buchhalter dann nochunsigned decimal byte8undsigned decimal byte8, deren Wertebereich auf 0–10 bzw. -10 bis +10 beschränkt ist; für Buchhalter, die auch Byte-Kosten mitrechnen, eincentimal byte8mit 0–100 bzw. -100 bis +100; einen ungefähr ausreichenden Typ für dasage-Feld in Datenbanken; und natürlich auchfloat byte8.rand()kaputt sein soll. Es erzeugt Werte, die zufällig aussehen, und genau das ist sein Zweck.Dass es keine kryptografisch sicheren Zufallszahlen erzeugt, ist selbstverständlich, und entsprechende Funktionen in anderen Sprachen tun das auch nicht. Wenn man schnell berechnete, einigermaßen zufällige Ganzzahlen braucht, funktioniert
rand()völlig ausreichend.Danke für das Interesse an dem Vorschlag; auf Basis des bisherigen Feedbacks habe ich einen aktualisierten Entwurf erstellt: https://isocpp.org/files/papers/D3477R1.html
Besonders eindrucksvoll ist der Satz: „Die Frage ist nicht, ob es noch Architekturen gibt, auf denen ein Byte nicht 8 Bit hat. Die gibt es! Die Frage ist, ob ihnen modernes C++ wichtig ist – und ob sie modernem C++ wichtig sind.“
Bei diesem Vorschlag bin ich zwiegespalten. Einerseits ist er offensichtlich richtig, und es gibt keinen sinnvollen Anwendungsfall dafür, dass
CHAR_BITnicht 8 ist.Andererseits fühlt es sich auch so an, als würde man einer Just-World-Sicht nachgeben, nach der die Welt allein auf Basis eines persönlichen, übermäßig vereinfachten Modells des Computerinneren sinnvoll und nachvollziehbar sein müsse. Mit so einem Ansatz kommt man ziemlich weit, aber letztlich endet er in einer Sackgasse; am Ende muss man akzeptieren, dass man nichts weiß und dass das Beste, was man hat, ein formales Argument ist, dass man nur unter der Bedingung, dass die Dokumentation stimmt, ein korrektes Programm konstruiert hat. Das ist ein großer gedanklicher Sprung, und persönlich fiel es mir umso schwerer, ihn später zu machen, je länger ich nicht gezwungen war, ihn anzuerkennen. Trotzdem scheinen physische Elektronikprojekte unter Einsteigern heute beliebt zu sein; vielleicht wird also statt „Lies die Dokumentation“ künftig „Lies das verdammte Datenblatt“ zum neuen Standard.
autoconf-Skript ausführe, dass die Anzahl der Bits pro Byte geprüft und inconfig.hgespeichert wird. Als hätte irgendjemand tatsächlich vor, je nach diesem Wert zu handeln.Man bräuchte eine Möglichkeit, den gesamten bestehenden Code im Zuge der Kompilierung daraufhin zu prüfen, ob dieses Makro bereits verwendet wird. Solche Breaking Changes bergen auch das Risiko, die Sprache zu spalten. Es ist auch nicht klar, wie schwierig es wäre zu testen, ob bestehende Codebases das
CHAR_BIT-Makro verwenden und ob sie auf einen neuen Compiler aktualisiert werden können. Fragen wären auch, welche Bibliotheken als kaputt gelten würden und ob es Probleme bei der Interaktion mit anderem Code gibt, der mit Verwendung vonCHAR_BITkompiliert wurde. Ich stimme zu, dass das kontraintuitiv ist, aber besser wäre es, zuerst Konvertierungswerkzeuge zu bauen, zu zeigen, dass es selbst in Extremfällen sicher ist, und dann umzustellen.Mir gefällt der Vorschlag, weil er zugleich unumstritten und unglaublich scharf ist.
int8_t == char == 8 bitszu erzwingen, ist für mich völlig in Ordnung, aber ich bin nicht sicher, ob man das Missverständnis verbreiten sollte, ein Byte sei 8 Bit.Ein 8-Bit-Byte nennt man Oktett (octet). Gleichzeitig ist
byteseit C++17 bereits so etwas wie ein „Alias“ fürchar: https://en.cppreference.com/w/cpp/types/byteIn den 45 Jahren danach habe ich nie erlebt, dass „Byte“ etwas anderes bedeutete; wenn es also eine Definition von „Byte“ gibt, die nicht 8 Bit meint, bräuchte ich dafür eine Quelle.
Das ist keine deskriptive Aussage, sondern eine normative Aussage.
int8 == signed charnicht.std::cout << (int8_t)32 << std::endl;sollte selbstverständlich 32 ausgeben.Hat zwar nichts mit C++ zu tun, aber die Idee eines Retro-Mikrocomputers mit 6-Bit-Bytes gefällt mir ziemlich gut. 24 Bit wären dann ein Wort.
Mikrocomputer arbeiten meist mit einer kleinen Zahl von Objekten und bevorzugen Arrays gegenüber Pointern, sodass man Speicher sparen könnte. VGA hatte 6 Bit pro Farbe, mit einer 6x4-Bit-Matrix lassen sich lesbare Buchstaben erstellen, grundlegende LISP- oder Forth-Sprachen passen ebenfalls in ein 6-Bit-Alphabet, und das ursprüngliche System/360 nutzte nur 24-Bit-Adressen. 12 MiB Speicher mit unabhängig adressierbaren 6-Bit-Einheiten sollten für jeden genug sein. Falls es nicht reicht, könnte man FAT-12 ganz natürlich zu FAT-24 erweitern oder 48-Bit-Pointer verwenden, die ungefähr so nützlich sind wie 64-Bit-Pointer.