Discussion:
Big vs. Little endian
(zu alt für eine Antwort)
Thomas Koenig
2018-08-17 11:44:34 UTC
Permalink
Der Satz aus https://developer.ibm.com/linuxonpower/porting-guide/

"POWER uses the same byte order as x86 architectures so compiling
tools and working on POWER is almost exactly like other setups."

sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Rainer Weikusat
2018-08-17 18:05:18 UTC
Permalink
Post by Thomas Koenig
Der Satz aus https://developer.ibm.com/linuxonpower/porting-guide/
"POWER uses the same byte order as x86 architectures so compiling
tools and working on POWER is almost exactly like other setups."
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*], und "Network
byte order ist so wie UNSERE CPUs das machen" (naemlich Sun-CPUs) ist
nicht wirklich anders als "so wie PCs das machen" ...

[*] 'Am Puls der Zeit' koennte man behaupten, dass die Zumutung, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10 zu zaehlen (und nicht 5, 3, 8, 4, 10, 1, 7, 6, 9, 2
oder 3, 1, 7, 9, 10, 8, 4, 6, 5, 2 oder 9, 2, 4, 8, 5, 7, 3, 10, 6, 1),
Hardware-Ingenieure traumatisieren wuerde :->
Peter J. Holzer
2018-08-18 21:44:49 UTC
Permalink
Post by Rainer Weikusat
Post by Thomas Koenig
Der Satz aus https://developer.ibm.com/linuxonpower/porting-guide/
"POWER uses the same byte order as x86 architectures so compiling
tools and working on POWER is almost exactly like other setups."
Hmm. Ich habe vor ca. 10 Jahren mich mal kurz mit Linux auf Power
gespielt (wir haben damals überlegt, IBM-pSeries-Server anzuschaffen).
Meiner Erinnerung nach war das Big-Endian. [befragt die Ente]. Aha,
offenbar haben die Distributionen ca. 2014 umgestellt.
Post by Rainer Weikusat
Post by Thomas Koenig
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*],
Ich bin mir nicht sicher, was Du hier mit "explizit" meinst. Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist. Wenn man natürlich glaubt, C-Structs direkt mit
read befüllen zu müssen (ob aus Optimierungsgründen oder
Ahnungslosigkeit oder beidem), handelt man sich solche
Plattformabhängigkeiten ein.
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.

Ich halte es für wahrscheinlich, dass sich Postel und Co an der Hardware
orientiert haben, die sie zur Verfügung hatten. Aber ob sie dabei von
"oben" (der Endianness der CPU, falls die überhaupt eine hatte) oder von
"unten" (der Endianness der Bits auf der Leitung) ausgingen, entzieht
sich meiner Kenntnis (RS-232 ist zwar traditionell little-endian[1],
aber ich weiß nicht, was die IMPs verwendet haben).

Den Ausdruck "network byte order" mag Sun erfunden haben (soweit ich
sehe, wird er erstmals in RFC 951 erwähnt, an dem ein Sun-Mitarbeiter
beteiligt war), aber die Festlegung für zentrale Internet-Protokolle
erfolgte lang vorher.

hp

[1] Der Standard spezifiziert das interessanterweise gar nicht, ein
kurioses Versäumnis.
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Michael Bäuerle
2018-08-20 07:30:55 UTC
Permalink
[...] Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist. Wenn man natürlich glaubt, C-Structs direkt mit
read befüllen zu müssen (ob aus Optimierungsgründen oder
Ahnungslosigkeit oder beidem), handelt man sich solche
Plattformabhängigkeiten ein.
Ich hatte diesen Fall einmal auf dem Tisch:
Kommunikation mit einem Gerät via RS232 bzw. USB und die Bibliothek auf
dem PC hat die empfangenen Daten direkt als C-Structs interpretiert.
Als diese Bibliothek dann auf ein Embedded-System portiert wurde, musste
man nicht nur bei Little-Endian bleiben, sondern der Compiler musste
auch identische Datentypen und ein identisches Struct-Layout verwenden.

Die Alternative wäre gewesen alles neu zu schreiben, das sollte aus
verschiedenen Gründen vermieden werden.
Peter J. Holzer
2018-08-21 16:20:41 UTC
Permalink
[...] Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist. Wenn man natürlich glaubt, C-Structs direkt mit
read befüllen zu müssen (ob aus Optimierungsgründen oder
Ahnungslosigkeit oder beidem), handelt man sich solche
Plattformabhängigkeiten ein.
Ich nicht nur einmal :-/

[...]

In den meisten Fällen, die mir untergekommen sind, hat das natürlich
schon mit allen möglichen Architekturen funktioniert (das war Ende der
80er-, Anfang der 90er-Jahre, als "portabel" noch nicht "Country UND
Western" hieß). Waren meistens ziemliche #ifdef-Orgien. Sicher nicht
einfacher, als das Zeug gleich portabel zu schreiben, und ob der Code
tatsächlich effizienter war, war zumindest zweifelhaft.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Michael Bäuerle
2018-08-21 18:47:08 UTC
Permalink
Post by Peter J. Holzer
[...] Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist. Wenn man natürlich glaubt, C-Structs direkt mit
read befüllen zu müssen (ob aus Optimierungsgründen oder
Ahnungslosigkeit oder beidem), handelt man sich solche
Plattformabhängigkeiten ein.
Ich nicht nur einmal :-/
[...]
In den meisten Fällen, die mir untergekommen sind, hat das natürlich
schon mit allen möglichen Architekturen funktioniert (das war Ende der
80er-, Anfang der 90er-Jahre, als "portabel" noch nicht "Country UND
Western" hieß). Waren meistens ziemliche #ifdef-Orgien. Sicher nicht
einfacher, als das Zeug gleich portabel zu schreiben, und ob der Code
tatsächlich effizienter war, war zumindest zweifelhaft.
In meinem Fall war seinerzeit vermutlich nur gefordert worden "läuft auf
PC mit Windows" (und das hat es auch getan wie gewünscht).
Rainer Weikusat
2018-08-22 17:17:22 UTC
Permalink
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Thomas Koenig
Der Satz aus https://developer.ibm.com/linuxonpower/porting-guide/
"POWER uses the same byte order as x86 architectures so compiling
tools and working on POWER is almost exactly like other setups."
Hmm. Ich habe vor ca. 10 Jahren mich mal kurz mit Linux auf Power
gespielt (wir haben damals überlegt, IBM-pSeries-Server anzuschaffen).
Meiner Erinnerung nach war das Big-Endian. [befragt die Ente]. Aha,
offenbar haben die Distributionen ca. 2014 umgestellt.
Post by Rainer Weikusat
Post by Thomas Koenig
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*],
Ich bin mir nicht sicher, was Du hier mit "explizit" meinst. Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist.
Das Resultat einer Serialisierung ist immer ein Bytestream (eigentlich
ist es meistens ein Bitstream ...). Was hier "automatisch
Endian-unabhaengig" sein soll, ist mir nicht klar: Den Wert eines
Ganzzahlobjektes mit einer Groesse > 1 kann man naiv serialisieren, dh
die Bytes/ chars von der Anfangsaddresses bis zur Endaddresses. Dann ist
das Ergebnis endian-abhaengig. Oder man kann den Wert mithilfe von
Bitoperationen manuell in Bytes zerlegen. Solange Sender und Empfaenger
dafuer dieselbe Konvention benutzen, spielt die konkrete
Host-Zahlenrepraesentation keine Rolle _aber_ eine Byte-Anordnung muss man
sich dann natuerlich aussuchen.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.
XDR ist big-endian und von Sun entwickelt. Und ich behaupte hier
schlankweg, dass die Endianness, die Sun damals fuer eigene Hardware
benutzt hatte, hier ausschlaggebender war, als "irgendwelche
Protokollheader-Konventionen". Schliesslich ist das nicht nur fuer die
eigenen Entwickler bequemer, es ist auch schneller.
Thomas Koenig
2018-08-22 22:18:44 UTC
Permalink
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.
XDR ist big-endian und von Sun entwickelt.
TCP ist aber auch schon big-endian, und das war früher
(und nicht von Suns).

Nach dem bisschen googeln war die erste Variante 1974, aber dazu
habe ich keine Doku gefunden. 1981 war der erste RFC für TCP/IP,
Sun wurde 1982 gegründet. Ich würde mal sagen, die waren unschuldig
daran :-)

Die erste Connection über TCPv1 war
lt. https://www.livinginternet.com/i/ii_tcpip.htm mit einer LSI-11,
eine kleinere PDP-11, und die war little-endian.

Leider habe ich zu den früheren Varianten keine Doku gefunden, aber
ich halte es jetzt eher für unwahrscheinlich, dass die das geändert
haben.
Post by Rainer Weikusat
Und ich behaupte hier
schlankweg, dass die Endianness, die Sun damals fuer eigene Hardware
benutzt hatte, hier ausschlaggebender war, als "irgendwelche
Protokollheader-Konventionen". Schliesslich ist das nicht nur fuer die
eigenen Entwickler bequemer, es ist auch schneller.
Die Behauptung scheint mir eher unwahrscheinlich.

In den 1970ern war ja noch nicht mal ausgemacht, dass 8-bit-Bytes
das Rennen machen würden. Schließlich gab es ja auch solche
Maschinen wie die CDC Cyber-Reihe, die 60-bit-Wörter mit "Bytes"
von je 12 Bit und 6-bit-Charactenr gearbeitet haben, oder die Univac
1100 (habe ich damals an der Uni nur knapp verpasst) und die PDP-10
mit 36 Bits pro Wort.
Rainer Weikusat
2018-08-23 12:49:36 UTC
Permalink
Post by Thomas Koenig
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.
XDR ist big-endian und von Sun entwickelt.
TCP ist aber auch schon big-endian, und das war früher
(und nicht von Suns).
Keineswegs. TCP (dh IP und TCP) schreiben eine bestimmte Byte-Anordnung
fuer Addressen, Ports und Laengen vor. Aber nicht fuer Nutzdaten.

[...]
Post by Thomas Koenig
Post by Rainer Weikusat
Und ich behaupte hier
schlankweg, dass die Endianness, die Sun damals fuer eigene Hardware
benutzt hatte, hier ausschlaggebender war, als "irgendwelche
Protokollheader-Konventionen". Schliesslich ist das nicht nur fuer die
eigenen Entwickler bequemer, es ist auch schneller.
Die Behauptung scheint mir eher unwahrscheinlich.
In den 1970ern war ja noch nicht mal ausgemacht, dass 8-bit-Bytes
das Rennen machen würden.
Von "1970ern" redet ja auch keiner, sondern von einem universellen
Datenserialisierungsformat, das in 1980ern von Sun urspruenglich fuer
NFS bzw SunRPC entworfen wurde. Das ist big endian, weil das eine damals
populaere Konvention fuer neue Computer, die fuer ernsthaftes Arbeiten
gedacht waren, war. Mittlerweile sind diese CPUs vor allem in Nischen zu
finden und lernen alle so nach und nach (insoweit das nicht bereits
geschehen ist), die andere Konvention zu unterstuetzen, weil es nunmal
Horden von Programmierern gibt, die diese fuer selbstverstaendlich
halten.

Es gibt keinen qualitativen Unterschied zwischen zwei inkompatiblen "so
wie wir das halt machen"-Konventionen.
Patrick.Schluter
2018-12-12 15:54:56 UTC
Permalink
Post by Thomas Koenig
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.
XDR ist big-endian und von Sun entwickelt.
TCP ist aber auch schon big-endian, und das war früher
(und nicht von Suns).
Nach dem bisschen googeln war die erste Variante 1974, aber dazu
habe ich keine Doku gefunden. 1981 war der erste RFC für TCP/IP,
Sun wurde 1982 gegründet. Ich würde mal sagen, die waren unschuldig
daran :-)
Die erste Connection über TCPv1 war
lt. https://www.livinginternet.com/i/ii_tcpip.htm mit einer LSI-11,
eine kleinere PDP-11, und die war little-endian.
PSP-11 war middle endian*, also weder little noch big endian. Es läuft
die Geschichte im Umlauf dass die erste Portierung von Unix auf PDP-11
sich mit nUxi gemeldet hat.

* middle endian: 0x01020304 wird 02 01 04 03 im Speicher abgelegt.
Post by Thomas Koenig
Leider habe ich zu den früheren Varianten keine Doku gefunden, aber
ich halte es jetzt eher für unwahrscheinlich, dass die das geändert
haben.
Post by Rainer Weikusat
Und ich behaupte hier
schlankweg, dass die Endianness, die Sun damals fuer eigene Hardware
benutzt hatte, hier ausschlaggebender war, als "irgendwelche
Protokollheader-Konventionen". Schliesslich ist das nicht nur fuer die
eigenen Entwickler bequemer, es ist auch schneller.
Die Behauptung scheint mir eher unwahrscheinlich.
In den 1970ern war ja noch nicht mal ausgemacht, dass 8-bit-Bytes
das Rennen machen würden. Schließlich gab es ja auch solche
Maschinen wie die CDC Cyber-Reihe, die 60-bit-Wörter mit "Bytes"
von je 12 Bit und 6-bit-Charactenr gearbeitet haben, oder die Univac
1100 (habe ich damals an der Uni nur knapp verpasst) und die PDP-10
mit 36 Bits pro Wort.
Peter J. Holzer
2018-08-24 09:48:25 UTC
Permalink
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Thomas Koenig
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*],
Ich bin mir nicht sicher, was Du hier mit "explizit" meinst. Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist.
Das Resultat einer Serialisierung ist immer ein Bytestream (eigentlich
ist es meistens ein Bitstream ...). Was hier "automatisch
Wenn man davon ausgeht, dass man einem Bytestream in einem vorgegebenen
Format erzeugen möchte, und das ganz naiv in Standard-C runterkodiert,
landet man automatisch bei einer endian-unabhängigen Implementierung.

Beispiel: Der IHDR-Chunk im PNG-Fileformat besteht aus Length, Chunk
type, Width, height (je 4 Bytes, big endian),. Bit depth, Color type,
Compression method, Filter method, Interlace method (je 1 Byte) und CRC
(4 Bytes, big endian).

Naiverweise mache ich daraus sowas:

struct ihdr {
uint_least32_t length;
uint_least8_t chunk_type[4];
uint_least32_t width;
uint_least32_t height;
uint_least8_t bit_depth;
enum color_type color_type;
enum compression_method compression_method;
enum filter_method filter_method;
enum interlace_method interlace_method;
uint_least32_t crc;
}

void write_ihdr(FILE *fp, struct ihdr *p) {
writeu4(fp, length);
writec(fp, chunk_type, 4);
writeu4(fp, width);
writeu4(fp, height);
writeu1(fp, bit_depth);
writeu1(fp, color_type);
writeu1(fp, compression_method);
writeu1(fp, filter_method);
writeu1(fp, interlace_method);
writeu4(fp, crc);
}

void writeu4(fp, uint_least32_t u) {
putc((u >> 24) & 0xFF);
putc((u >> 16) & 0xFF);
putc((u >> 8) & 0xFF);
putc((u >> 0) & 0xFF);
}

...

Das geht streng nach der Spezifikation des Fileformats vor und verlässt
sich auf nichts, was der C-Standard mir nicht garantiert. Das ist
vollkommen unabhängig von der Endianness und darüber hinaus auch von
Wortgrößen, Byte-Größe, Padding, etc. Der gleiche Code funktioniert auf
x86 (16, 32 oder 64 Bit), Sparc, Univac 1100 (36 bit)[1], etc.

Erst wenn ich anfange zu überlegen; "Das RAM besteht ja auch aus Bytes,
kann ich diese Struct nicht mit einem einzigen (f)write rausschreiben,
statt unzählige Male putc aufzurufen?" handle ich mir diese
Plattform-Abhängigkeiten ein. Dann ist die Endianness entscheidend (auf
x86 muss ich die 4-Byte-Felder swappen), dann sind die Wortgrößen
entscheidend (wenn ich auf der Univac ein 36-Bit-Wort als "bytes"
rausschreibe, bekomme ich wahrscheinlich nicht die unteren 32 Bits),
dann ist entscheidend, wie der Compiler ein enum implementiert, dann
stört mich das Padding, das da fast sicher vor der Checksumme eingebaut
wird, und möglicherweise noch ein paar andere Sachen.

In der Praxis hat man dann für jede Plattform eine eigene Variante, oft
mittels #ifdef zusammengewürfelt. In manchen Fällen wird der
Performance-Gewinn die größere Komplexität aufwiegen. In vielen Fällen
aber nicht - das war dann entweder premature optimization oder der
ursprüngliche Programmierer hat über den Tellerrand seiner Plattform
nicht hinausgedacht, und jeder, der das portiert hat, hat nur das
Minimum geändert.
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.
XDR ist big-endian und von Sun entwickelt.
Du hast von "network byte order" geschrieben. Und das assoziiere ich
jedenfalls mehr mit der Endianness von IP, TCP, UDP, ICMP, Telnet
(sic!), etc. als mit XDR. Dass XDR die gleiche Endianness verwendet, mag
ein glücklicher Zufall sein oder gewollt, es ändert nichts daran, dass
big-endian Standard in der IP-Welt war, bevor Sun überhaupt gegründet
wurde.

hp

[1] Die habe ich bei uns knapp versäumt.
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2018-08-24 15:25:16 UTC
Permalink
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Thomas Koenig
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*],
Ich bin mir nicht sicher, was Du hier mit "explizit" meinst. Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist.
Das Resultat einer Serialisierung ist immer ein Bytestream (eigentlich
ist es meistens ein Bitstream ...). Was hier "automatisch
,----
| Den Wert eines
| Ganzzahlobjektes mit einer Groesse > 1 kann man naiv serialisieren, dh
| die Bytes/ chars von der Anfangsaddresses bis zur Endaddresses. Dann ist
| das Ergebnis endian-abhaengig.
`----
Post by Peter J. Holzer
Wenn man davon ausgeht, dass man einem Bytestream in einem vorgegebenen
Format erzeugen möchte, und das ganz naiv in Standard-C runterkodiert,
landet man automatisch bei einer endian-unabhängigen Implementierung.
Im wirklichen Universum (von dem vielleicht schon mal jemand gehoert
hat), gibt es Dinge wie "Linux", die sowas allerdings mit structs
machen. Oder UNIX-Systemaufrufe, die int * uebernehmen. Hier ergibt sich
nichts "automatisch", es sei denn, man hat ausdruecklich die Absicht,
strikt konformes C zu schreiben. Das mag unter besonderen Umstaenden
sinnvoll sein, aber in vielen Faellen ist es das nicht.
Post by Peter J. Holzer
void writeu4(fp, uint_least32_t u) {
putc((u >> 24) & 0xFF);
putc((u >> 16) & 0xFF);
putc((u >> 8) & 0xFF);
putc((u >> 0) & 0xFF);
}
,----
| Oder man kann den Wert mithilfe von Bitoperationen manuell in Bytes
| zerlegen. Solange Sender und Empfaenger dafuer dieselbe Konvention
| benutzen, spielt die konkrete Host-Zahlenrepraesentation keine Rolle
| _aber_ eine Byte-Anordnung muss man sich dann natuerlich aussuchen.
`----

Das ist dann sowas, wie das da oben, und hier benutzt es big-endian weil
es das tut und eine Menge ueberfluessiger Operations aufgrund von
unguenstiger Programmierung.

---
#include <inttypes.h>

uint8_t *put_u32(uint32_t x, uint8_t *p)
{
*p++ = x;
*p++ = x >> 8;
*p++ = x >> 16;
*p++ = x >> 24;

return p;
}
---

Es gibt eine guten Grund, sich damit bloss deswegen mehr Arbeit zu
machen, als notwendig, weil "irgendein Mainframe-Programmierer" es dann
vielleicht verwenden koennte. Der mag sich selber um die Wirrnisse
seiner Umgebung kuemmern.

Korollar: Software, die sinnvolles Hardwareverhalten vorraussetzt, ist
eine gute Bremse fuer allzu enthusiastisches Erfinden eigener
Zahlensysteme "weil das bestimmt fuer irgendetwas gut ist" :->>.

[...]
Post by Peter J. Holzer
Erst wenn ich anfange zu überlegen; "Das RAM besteht ja auch aus Bytes,
kann ich diese Struct nicht mit einem einzigen (f)write rausschreiben,
statt unzählige Male putc aufzurufen?" handle ich mir diese
Plattform-Abhängigkeiten ein.
Das halte ich fuer einen Irrtum: C legt nicht fest, welche Breite ein
unsigned char hat, folglicherweise resultiert write4u in einem bitstream
unbekannter Laenge und der kann auf einer Maschinen, deren char-Breite
eine andere ist, nicht ohne weiteres gelesen werden. Im Gegensatz dazu
produziert die uint8_t-Variante eine portable Serialisierung, deren
Resultat man mit geeigneten Klimmzuegen in jeder anderen Umgebung
verarbeiten kann.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.
XDR ist big-endian und von Sun entwickelt.
Du hast von "network byte order" geschrieben. Und das assoziiere ich
jedenfalls mehr mit der Endianness von IP, TCP, UDP, ICMP, Telnet
(sic!), etc. als mit XDR.
Der Begriff mag ungluecklich gewaehlt sein. Aber jedenfalls findet sich
im urspruenglichen XDR RFC folgender Text:

(2) Why is there only one byte-order for an XDR unit?

Supporting two byte-orderings requires a higher level protocol for
determining in which byte-order the data is encoded.

[...]

(3) Why is the XDR byte-order big-endian instead of little-endian?
Isn't this unfair to little-endian machines such as the VAX(r), which
has to convert from one form to the other?

Yes, it is unfair, but having only one byte-order means you have to
be unfair to somebody. Many architectures, such as the Motorola
68000* and IBM 370*, support the big-endian byte-order.

Die einleitende Behauptung ist mehr oder minder eine Ausrede: Man haette
einen Typ "byte order mark" definieren koennen, und zu Beginn eines
XDR-Datenstroms vorschreiben. Das kann man zwar "higher level protocol"
nennen, aber ein groesser Aufwand waere es jedenfalls nicht gewesen.

Der zweite zitierte Abschnitt ist ein Beweis der Behauptung: Das, was
wir hier entworfen haben, ist zwangslaeufig technich nachteilig fuer
manche Architekturen, und aber jedenfalls nicht fuer unsere.
Rainer Weikusat
2018-08-24 15:27:03 UTC
Permalink
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Thomas Koenig
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*],
Ich bin mir nicht sicher, was Du hier mit "explizit" meinst. Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist.
Das Resultat einer Serialisierung ist immer ein Bytestream (eigentlich
ist es meistens ein Bitstream ...). Was hier "automatisch
,----
| Den Wert eines
| Ganzzahlobjektes mit einer Groesse > 1 kann man naiv serialisieren, dh
| die Bytes/ chars von der Anfangsaddresses bis zur Endaddresses. Dann ist
| das Ergebnis endian-abhaengig.
`----
Post by Peter J. Holzer
Wenn man davon ausgeht, dass man einem Bytestream in einem vorgegebenen
Format erzeugen möchte, und das ganz naiv in Standard-C runterkodiert,
landet man automatisch bei einer endian-unabhängigen Implementierung.
Im wirklichen Universum (von dem vielleicht schon mal jemand gehoert
hat), gibt es Dinge wie "Linux", die sowas allerdings mit structs
machen. Oder UNIX-Systemaufrufe, die int * uebernehmen. Hier ergibt sich
nichts "automatisch", es sei denn, man hat ausdruecklich die Absicht,
strikt konformes C zu schreiben. Das mag unter besonderen Umstaenden
sinnvoll sein, aber in vielen Faellen ist es das nicht.
Post by Peter J. Holzer
void writeu4(fp, uint_least32_t u) {
putc((u >> 24) & 0xFF);
putc((u >> 16) & 0xFF);
putc((u >> 8) & 0xFF);
putc((u >> 0) & 0xFF);
}
,----
| Oder man kann den Wert mithilfe von Bitoperationen manuell in Bytes
| zerlegen. Solange Sender und Empfaenger dafuer dieselbe Konvention
| benutzen, spielt die konkrete Host-Zahlenrepraesentation keine Rolle
| _aber_ eine Byte-Anordnung muss man sich dann natuerlich aussuchen.
`----

Das ist dann sowas, wie das da oben, und hier benutzt es big-endian weil
es das tut und eine Menge ueberfluessiger Operations aufgrund von
unguenstiger Programmierung.

---
#include <inttypes.h>

uint8_t *put_u32(uint32_t x, uint8_t *p)
{
*p++ = x;
*p++ = x >> 8;
*p++ = x >> 16;
*p++ = x >> 24;

return p;
}
---

Es gibt eine guten Grund, sich damit bloss deswegen mehr Arbeit zu
machen, als notwendig, weil "irgendein Mainframe-Programmierer" es dann
vielleicht verwenden koennte. Der mag sich selber um die Wirrnisse
seiner Umgebung kuemmern.

Korollar: Software, die sinnvolles Hardwareverhalten vorraussetzt, ist
eine gute Bremse fuer allzu enthusiastisches Erfinden eigener
Zahlensysteme "weil das bestimmt fuer irgendetwas gut ist" :->>.

[...]
Post by Peter J. Holzer
Erst wenn ich anfange zu überlegen; "Das RAM besteht ja auch aus Bytes,
kann ich diese Struct nicht mit einem einzigen (f)write rausschreiben,
statt unzählige Male putc aufzurufen?" handle ich mir diese
Plattform-Abhängigkeiten ein.
Das halte ich fuer einen Irrtum: C legt nicht fest, welche Breite ein
unsigned char hat, folglicherweise resultiert write4u in einem bitstream
unbekannter Laenge und der kann auf einer Maschinen, deren char-Breite
eine andere ist, nicht ohne weiteres gelesen werden. Im Gegensatz dazu
produziert die uint8_t-Variante eine portable Serialisierung, deren
Resultat man mit geeigneten Klimmzuegen in jeder anderen Umgebung
verarbeiten kann.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
und "Network byte order ist so wie UNSERE CPUs das machen" (naemlich
Sun-CPUs) ist nicht wirklich anders als "so wie PCs das machen" ...
Es ist mir neu, dass es 1976 schon Sun-CPUs gab.
XDR ist big-endian und von Sun entwickelt.
Du hast von "network byte order" geschrieben. Und das assoziiere ich
jedenfalls mehr mit der Endianness von IP, TCP, UDP, ICMP, Telnet
(sic!), etc. als mit XDR.
Der Begriff mag ungluecklich gewaehlt sein. Aber jedenfalls findet sich
im urspruenglichen XDR RFC folgender Text:

(2) Why is there only one byte-order for an XDR unit?

Supporting two byte-orderings requires a higher level protocol for
determining in which byte-order the data is encoded. Since XDR is
not a protocol, this can't be done.

[...]

(3) Why is the XDR byte-order big-endian instead of little-endian?
Isn't this unfair to little-endian machines such as the VAX(r), which
has to convert from one form to the other?

Yes, it is unfair, but having only one byte-order means you have to
be unfair to somebody. Many architectures, such as the Motorola
68000* and IBM 370*, support the big-endian byte-order.

Die einleitende Behauptung ist mehr oder minder eine Ausrede: Man haette
einen Typ "byte order mark" definieren koennen, und zu Beginn eines
XDR-Datenstroms vorschreiben. Das kann man zwar "higher level protocol"
nennen, aber ein groesser Aufwand waere es jedenfalls nicht gewesen.

Der zweite zitierte Abschnitt ist ein Beweis der Behauptung: Das, was
wir hier entworfen haben, ist zwangslaeufig technich nachteilig fuer
manche Architekturen, und aber jedenfalls nicht fuer unsere.
Helmut Schellong
2018-08-24 15:47:03 UTC
Permalink
[...]
Post by Rainer Weikusat
Post by Peter J. Holzer
void writeu4(fp, uint_least32_t u) {
putc((u >> 24) & 0xFF);
putc((u >> 16) & 0xFF);
putc((u >> 8) & 0xFF);
putc((u >> 0) & 0xFF);
}
,----
| Oder man kann den Wert mithilfe von Bitoperationen manuell in Bytes
| zerlegen. Solange Sender und Empfaenger dafuer dieselbe Konvention
| benutzen, spielt die konkrete Host-Zahlenrepraesentation keine Rolle
| _aber_ eine Byte-Anordnung muss man sich dann natuerlich aussuchen.
`----
Das ist dann sowas, wie das da oben, und hier benutzt es big-endian weil
es das tut und eine Menge ueberfluessiger Operations aufgrund von
unguenstiger Programmierung.
Das MSB wird oben stets zuerst geschrieben, unabhängig
von der Endianess.
Beispielsweise, um auszudrucken.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Rainer Weikusat
2018-08-24 16:15:29 UTC
Permalink
Post by Helmut Schellong
[...]
Post by Rainer Weikusat
Post by Peter J. Holzer
void writeu4(fp, uint_least32_t u) {
putc((u >> 24) & 0xFF);
putc((u >> 16) & 0xFF);
putc((u >> 8) & 0xFF);
putc((u >> 0) & 0xFF);
}
,----
| Oder man kann den Wert mithilfe von Bitoperationen manuell in Bytes
| zerlegen. Solange Sender und Empfaenger dafuer dieselbe Konvention
| benutzen, spielt die konkrete Host-Zahlenrepraesentation keine Rolle
| _aber_ eine Byte-Anordnung muss man sich dann natuerlich aussuchen.
`----
Das ist dann sowas, wie das da oben, und hier benutzt es big-endian weil
es das tut und eine Menge ueberfluessiger Operations aufgrund von
unguenstiger Programmierung.
Das MSB wird oben stets zuerst geschrieben, unabhängig
von der Endianess.
Beispielsweise, um auszudrucken.
"MSB zuerst" ist big endian. Der oa Code erzeugt somit ein "big
endian"-Format unabhaenging von der Endianness der Implementierung.
Peter J. Holzer
2018-08-24 17:22:07 UTC
Permalink
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Thomas Koenig
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*],
Ich bin mir nicht sicher, was Du hier mit "explizit" meinst. Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist.
Das Resultat einer Serialisierung ist immer ein Bytestream (eigentlich
ist es meistens ein Bitstream ...). Was hier "automatisch
,----
| Den Wert eines
| Ganzzahlobjektes mit einer Groesse > 1 kann man naiv serialisieren, dh
| die Bytes/ chars von der Anfangsaddresses bis zur Endaddresses. Dann ist
| das Ergebnis endian-abhaengig.
`----
Post by Peter J. Holzer
Wenn man davon ausgeht, dass man einem Bytestream in einem vorgegebenen
Format erzeugen möchte, und das ganz naiv in Standard-C runterkodiert,
landet man automatisch bei einer endian-unabhängigen Implementierung.
Im wirklichen Universum (von dem vielleicht schon mal jemand gehoert
hat), gibt es Dinge wie "Linux", die sowas allerdings mit structs
machen.
Was ist "sowas"? Wie Du vielleicht gesehen hast, verwendet mein Code
auch eine struct. Allerdings nur im RAM, nicht, um on-disk Strukturen
abzubilden. (Und in echtem Code hätte die struct 3 Felder weniger, weil
ich die im RAM gar nie brauche.)

Und ja, ich weiß, dass der Linux-Kernel-Code voll von structs ist, die
on-disk (oder on-wire) Strukturen abbilden. Das heißt nicht unbedingt,
dass ich das für sinnvoll erachten muss.

Als Applikationsprogrammierer kann es mir aber auch egal sein, wie der
Kernel
Post by Rainer Weikusat
Oder UNIX-Systemaufrufe, die int * uebernehmen. Hier ergibt sich
nichts "automatisch", es sei denn, man hat ausdruecklich die Absicht,
strikt konformes C zu schreiben. Das mag unter besonderen Umstaenden
sinnvoll sein, aber in vielen Faellen ist es das nicht.
Post by Peter J. Holzer
void writeu4(fp, uint_least32_t u) {
putc((u >> 24) & 0xFF);
putc((u >> 16) & 0xFF);
putc((u >> 8) & 0xFF);
putc((u >> 0) & 0xFF);
}
,----
| Oder man kann den Wert mithilfe von Bitoperationen manuell in Bytes
| zerlegen. Solange Sender und Empfaenger dafuer dieselbe Konvention
| benutzen, spielt die konkrete Host-Zahlenrepraesentation keine Rolle
| _aber_ eine Byte-Anordnung muss man sich dann natuerlich aussuchen.
`----
Das ist dann sowas, wie das da oben, und hier benutzt es big-endian weil
es das tut
Nein, "es" (also dieser Code) benutzt Big-Endian weil, die
File-Format-Spezifikation das so vorgibt.

Auf einer "typischen" Big-Endian-Plattform könnte man das zu

void writeu4(fp, uint_least32_t u) {
fwrite(&u, sizeof(u), 1, fp);
}

vereinfachen.

Und auf einer "typischen" Little-Endian-Plattform zu:

void writeu4(fp, uint_least32_t u) {
uint32_t t = (u & 0xFF) << 24 | (u & 0xFF00) << 8 | (u & 0xFF0000) >> 8 | (u & 0xFF000000 >> 24;
fwrite(&t, sizeof(t), 1, fp);
}

Der "typische" Code (wie ich ihn immer und immer wieder gesehen habe),
wäre dann sowas:

void writeu4(fp, uint_least32_t u) {
#if BIG_ENDIAN
unint32_t t = u;
#elif LITTLE_ENDIAN
uint32_t t = (u & 0xFF) << 24 | (u & 0xFF00) << 8 | (u & 0xFF0000) >> 8 | (u & 0xFF000000 >> 24;
#else
#error "Platform not supported"
#endif
fwrite(&t, sizeof(t), 1, fp);
}

Oder wahrscheinlich eher sowas:

void writeu4(fp, uint_least32_t u) {
#if SUNOS && SUNOS_SPARC || IRIX || AIX || __LINUX && __MIPS || BSD || OBSCURIX
unint32_t t = u;
#elif SUNOS && SUNOS_X86 || ULTRIX || __OSF1 || __LINUX && (__X86 || __MIPSEL) || MS_DOS
uint32_t t = (u & 0xFF) << 24 | (u & 0xFF00) << 8 | (u & 0xFF0000) >> 8 | (u & 0xFF000000 >> 24;
#else
#error "Platform not supported"
#endif
fwrite(&t, sizeof(t), 1, fp);
}

Und das ist der einfache Fall. Für ganze Structs wird es komplizierter
(vor allem, wenn man wie hier Padding loswerden muss, was gar nicht bei
allen Compilern möglich ist) oder wenn Bitfields vorkommen.
Post by Rainer Weikusat
und eine Menge ueberfluessiger Operations aufgrund von
unguenstiger Programmierung.
Wie gesagt, die Implementation ist naiv. Ob sie ungünstig ist, kommt
darauf an. Sie ist jedenfalls ohne Kenntnisse des konkreten Compilers
les- und validierbar. Sie ruft für den ganzen Header 25 mal putc auf
statt einmal fwrite. Das braucht (fast) sicher mehr CPU-Zeit. Ob das
relevant ist, müsste man für eine konkrete Applikation messen.
Post by Rainer Weikusat
Post by Peter J. Holzer
Erst wenn ich anfange zu überlegen; "Das RAM besteht ja auch aus Bytes,
kann ich diese Struct nicht mit einem einzigen (f)write rausschreiben,
statt unzählige Male putc aufzurufen?" handle ich mir diese
Plattform-Abhängigkeiten ein.
Das halte ich fuer einen Irrtum: C legt nicht fest, welche Breite ein
unsigned char hat, folglicherweise resultiert write4u in einem bitstream
unbekannter Laenge und der kann auf einer Maschinen, deren char-Breite
eine andere ist, nicht ohne weiteres gelesen werden.
"Bitstreams" sind irrelevant. Die Einheit, auf der heute alle
Fileformate und Protokolle aufbauen ist das Octet. Wenn eine
Univac-1100 (ich glaube, es gibt noch ein paar, die produktiv im Einsatz
sind) ein PNG-File liest, dann werden da ziemlich sicher nicht 9 Octets
des Files in 2 36-Bit-Worte gepackt, sondern jeweils ein Octet in einen
9-Bit unsigned char[1]. Der Code funktioniert also ziemlich sicher so wie
er ist, weil jede andere Implementation unsinnig wäre. Ich habe mit DSPs
gearbeitet, wo CHAR_BIT==32 war - aber das keine "hosted" Implementation
und es gab keine stdio.
Post by Rainer Weikusat
Im Gegensatz dazu produziert die uint8_t-Variante eine portable
Serialisierung, deren Resultat man mit geeigneten Klimmzuegen in jeder
anderen Umgebung verarbeiten kann.
uint8_t muss es nicht geben. Dort wo es existiert, ist ist es identisch
mit unsigned char, Du hast also nichts gewonnen.

(Tatsächlich würde ich heute auf Architekturen, wo CHAR_BIT != 8 ist,
auch keine Rücksicht mehr nehmen, aber das ist ein anderes Thema. Worum
es mir hier geht, ist dass Fileformate eben eine Folge von Octets sind
und keine Folge von C-Typen (nicht mal primitive Typen wie short oder
long, geschweige denn komplexe Typen wie struct), und dass man das
berücksichtigen sollte.)

hp

[1] So steht's auch schon in K&R I, wenn auch für eine Honeywell 6000.
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2018-08-24 17:50:16 UTC
Permalink
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Thomas Koenig
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Software leichter portiert werden kann...)
Hmm ... naja ... es macht immer dann einen Unterschied, wenn Binaerdaten
serialisiert oder deserialisiert werden muessen. Natuerlich kann man
fordern, dass in solchen Faellen immer eine explizite Byteanordnung
benutzt werden sollte, aber das 'muss' nur sein, weil Leute sich
historisch nicht auf eine einheitliche einigen konnten[*],
Ich bin mir nicht sicher, was Du hier mit "explizit" meinst. Tatsache
ist, dass die meisten Binär-Protokolle das Format eines Byte-Streams
vorgeben. Wenn man das so programmiert (also einen Byte-Stream liest und
schreibt) erhält man automatisch ein Programm, das von der Endianness
(und anderen Eigenschaften der Implementation, wie Typgrößen, Padding,
etc.) unabhängig ist.
Das Resultat einer Serialisierung ist immer ein Bytestream (eigentlich
ist es meistens ein Bitstream ...). Was hier "automatisch
,----
| Den Wert eines
| Ganzzahlobjektes mit einer Groesse > 1 kann man naiv serialisieren, dh
| die Bytes/ chars von der Anfangsaddresses bis zur Endaddresses. Dann ist
| das Ergebnis endian-abhaengig.
`----
Post by Peter J. Holzer
Wenn man davon ausgeht, dass man einem Bytestream in einem vorgegebenen
Format erzeugen möchte, und das ganz naiv in Standard-C runterkodiert,
landet man automatisch bei einer endian-unabhängigen Implementierung.
Im wirklichen Universum (von dem vielleicht schon mal jemand gehoert
hat), gibt es Dinge wie "Linux", die sowas allerdings mit structs
machen.
Was ist "sowas"? Wie Du vielleicht gesehen hast, verwendet mein Code
auch eine struct. Allerdings nur im RAM, nicht, um on-disk Strukturen
abzubilden. (Und in echtem Code hätte die struct 3 Felder weniger, weil
ich die im RAM gar nie brauche.)
Und ja, ich weiß, dass der Linux-Kernel-Code voll von structs ist, die
on-disk (oder on-wire) Strukturen abbilden. Das heißt nicht unbedingt,
dass ich das für sinnvoll erachten muss.
Ich halte das auch nicht fuer sinnvoll. Aber es ist ueblich, dh andere
Leute, und zwar nicht eben wenige, halten das fuer sinnvoll.

An dieser Stelle koennte man wunderbar eine Bauhaus'sche
Standardargumention "seltsames Geheimwissen als des Eigenutzes
verdaechtigem Selbstzweck" zugunsten von structs und contra
Bitoperationen einfuegen ...


[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
void writeu4(fp, uint_least32_t u) {
putc((u >> 24) & 0xFF);
putc((u >> 16) & 0xFF);
putc((u >> 8) & 0xFF);
putc((u >> 0) & 0xFF);
}
,----
| Oder man kann den Wert mithilfe von Bitoperationen manuell in Bytes
| zerlegen. Solange Sender und Empfaenger dafuer dieselbe Konvention
| benutzen, spielt die konkrete Host-Zahlenrepraesentation keine Rolle
| _aber_ eine Byte-Anordnung muss man sich dann natuerlich aussuchen.
`----
Das ist dann sowas, wie das da oben, und hier benutzt es big-endian weil
es das tut
Nein, "es" (also dieser Code) benutzt Big-Endian weil, die
File-Format-Spezifikation das so vorgibt.
Auf einer "typischen" Big-Endian-Plattform könnte man das zu
void writeu4(fp, uint_least32_t u) {
fwrite(&u, sizeof(u), 1, fp);
}
vereinfachen.
void writeu4(fp, uint_least32_t u) {
uint32_t t = (u & 0xFF) << 24 | (u & 0xFF00) << 8 | (u & 0xFF0000) >> 8 | (u & 0xFF000000 >> 24;
fwrite(&t, sizeof(t), 1, fp);
}
Der "typische" Code (wie ich ihn immer und immer wieder gesehen habe),
void writeu4(fp, uint_least32_t u) {
#if BIG_ENDIAN
unint32_t t = u;
#elif LITTLE_ENDIAN
uint32_t t = (u & 0xFF) << 24 | (u & 0xFF00) << 8 | (u & 0xFF0000) >> 8 | (u & 0xFF000000 >> 24;
#else
#error "Platform not supported"
#endif
fwrite(&t, sizeof(t), 1, fp);
}
usf

Man kann das aber auch, wie im Beispiel angedeutet, in einen
Speicherpuffer serialisieren und den mit einem einzigen $quasifumbel
schreiben.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
Erst wenn ich anfange zu überlegen; "Das RAM besteht ja auch aus Bytes,
kann ich diese Struct nicht mit einem einzigen (f)write rausschreiben,
statt unzählige Male putc aufzurufen?" handle ich mir diese
Plattform-Abhängigkeiten ein.
Das halte ich fuer einen Irrtum: C legt nicht fest, welche Breite ein
unsigned char hat, folglicherweise resultiert write4u in einem bitstream
unbekannter Laenge und der kann auf einer Maschinen, deren char-Breite
eine andere ist, nicht ohne weiteres gelesen werden.
"Bitstreams" sind irrelevant. Die Einheit, auf der heute alle
Fileformate und Protokolle aufbauen ist das Octet. Wenn eine
Univac-1100 (ich glaube, es gibt noch ein paar, die produktiv im Einsatz
sind) ein PNG-File liest, dann werden da ziemlich sicher nicht 9 Octets
des Files in 2 36-Bit-Worte gepackt, sondern jeweils ein Octet in einen
9-Bit unsigned char[1].
Damit etwas das lesen kann, muss die char-Groesse des erzeugenden
Systems bekannt sein.

[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Im Gegensatz dazu produziert die uint8_t-Variante eine portable
Serialisierung, deren Resultat man mit geeigneten Klimmzuegen in jeder
anderen Umgebung verarbeiten kann.
uint8_t muss es nicht geben. Dort wo es existiert, ist ist es identisch
mit unsigned char, Du hast also nichts gewonnen.
"muss es nicht geben" ist nicht ganz richtig:

These types are optional. However, if an implementation provides
integer types with widths of 8, 16, 32, or 64 bits, it shall
define the corresponding typedef names.

Gewonnen hat man hier im Zweifelsfall, dass die notwendige Breite
Bestandteil des Codes ist. Fuer sich genommen ist das ein kleiner
Vorteil, aber der Name ist ausserdem kuerzer. Und man kann sich die &
0xff sparen, weil man CHAR_BIT == 23 ohnehin nicht unterstuetzt.
Peter J. Holzer
2018-08-25 08:50:22 UTC
Permalink
Post by Rainer Weikusat
Man kann das aber auch, wie im Beispiel angedeutet, in einen
Speicherpuffer serialisieren und den mit einem einzigen $quasifumbel
schreiben.
Natürlich. Das ist genau das gleiche.
Post by Rainer Weikusat
Post by Peter J. Holzer
"Bitstreams" sind irrelevant. Die Einheit, auf der heute alle
Fileformate und Protokolle aufbauen ist das Octet. Wenn eine
Univac-1100 (ich glaube, es gibt noch ein paar, die produktiv im Einsatz
sind) ein PNG-File liest, dann werden da ziemlich sicher nicht 9 Octets
des Files in 2 36-Bit-Worte gepackt, sondern jeweils ein Octet in einen
9-Bit unsigned char[1].
Damit etwas das lesen kann, muss die char-Groesse des erzeugenden
Systems bekannt sein.
Bei Dir weiß ich sehr häufig nicht, was Du mit "das", "sowas", etc.
meinst. Das scheint sich semantisch oft auf etwas anderes zu beziehen
als grammatisch.

In diesem Absatz schreibe ich davon, dass ein File mit einem definierten
Fileformat auf einem System mit mehr als 8 Bits/char gelesen wird. In
diesem Fall ist es völlig irrelevant, welche char-Größe das erzeugende
System hatte (und ob die Software überhaupt in C oder sonst einer
Sprache mit einem Datentyp "char" geschrieben war). Alles, was zählt,
ist, dass das File selbst der Spezifikation entspricht. Dein "das" kann
sich also nicht darauf beziehen. Aber auf was dann? Auf den umgekehrten
Fall (File wird geschrieben, nicht gelesen)? Auch dann muss ich das
nicht wissen. Das Fileformat schreibt 8 Bits an Information pro Byte
vor. Mehr kann das erzeugende Programm nicht schreiben, sonst ist das
erzeugte File nicht mehr spezifikationskonform. Wenn ein Byte auf der
Platte 9 Bits belegt, dann bleibt halt ein Bit ungenutzt (so wie auf
einem 8-Bit-System 1 Bit ungenutzt bleibt, wenn man ASCII schreibt[1]).

hp

[1] Amüsantes Detail: In manchen älteren RFCs steht explizit, dass das
oberste Bit ungenutzt bleiben muss, nicht das unterste.
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Helmut Schellong
2018-08-25 09:02:56 UTC
Permalink
Post by Peter J. Holzer
[1] Amüsantes Detail: In manchen älteren RFCs steht explizit, dass das
oberste Bit ungenutzt bleiben muss, nicht das unterste.
Dazu fällt mir ein, daß auf einer big-endian-Plattform
auch die Bits in Bitfeldern in Strukturen umgedreht sind.

Gesehen auf einer Coldfire-Plattform.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Rainer Weikusat
2018-08-25 17:46:50 UTC
Permalink
Post by Peter J. Holzer
Post by Rainer Weikusat
Man kann das aber auch, wie im Beispiel angedeutet, in einen
Speicherpuffer serialisieren und den mit einem einzigen $quasifumbel
schreiben.
Natürlich. Das ist genau das gleiche.
Drei unterschiedliche Dinge koennen nicht "genau das gleiche" sein. Dh
man muss sich nicht zwischen "Endianabhaengig" und "Ruft 500x fwrite
auf" entscheiden: Anstatt die Daten via stdio in einen in der Bibliothek
versteckten Puffer zu kippen, durch dessen Benutzung man sich all
moeglichen anderen Nachteile einhandelt, wie zB "ein winziges
TCP-Segment mit einem drittel eines Protokollheaders wird jetzt
gesendet, die restlichen zwei Drittel plus die Nutzdaten plus zwei
Drittel des naechsten Headers in 15 Minuten und der Rest nie, weil
jemand den Computer ploetzlich ausgeschaltet hat", kann man eine
komplette Nachricht in einem Speicherpuffer endian-unabgaengig
zusammenbauen und zu einem bestimmten Zeitpunk als ganzes verschicken.

Damit haette man beide Probleme vermieden, also ist jeder dagegen :->.
Post by Peter J. Holzer
Post by Rainer Weikusat
Post by Peter J. Holzer
"Bitstreams" sind irrelevant. Die Einheit, auf der heute alle
Fileformate und Protokolle aufbauen ist das Octet. Wenn eine
Univac-1100 (ich glaube, es gibt noch ein paar, die produktiv im Einsatz
sind) ein PNG-File liest, dann werden da ziemlich sicher nicht 9 Octets
des Files in 2 36-Bit-Worte gepackt, sondern jeweils ein Octet in einen
9-Bit unsigned char[1].
Damit etwas das lesen kann, muss die char-Groesse des erzeugenden
Systems bekannt sein.
Bei Dir weiß ich sehr häufig nicht, was Du mit "das", "sowas", etc.
meinst. Das scheint sich semantisch oft auf etwas anderes zu beziehen
als grammatisch.
Man hat mir mal gesagt, ich wuerde mich "nichtlinear" ausdruecken, und
das stimmt wohl. Dumme Leute versuchen gelegentlich, dass durch
zusammenhangsloses, lineares Gefasel zu imitieren, und noch duemmere
fallen sogar darauf herein (an einem anderen Ort so geschehen).

Gehoert hier aber nicht her.
Post by Peter J. Holzer
In diesem Absatz schreibe ich davon, dass ein File mit einem definierten
Fileformat auf einem System mit mehr als 8 Bits/char gelesen wird. In
diesem Fall ist es völlig irrelevant, welche char-Größe das erzeugende
System hatte (und ob die Software überhaupt in C oder sonst einer
Sprache mit einem Datentyp "char" geschrieben war). Alles, was zählt,
ist, dass das File selbst der Spezifikation entspricht.
Das tut es aber nur, wenn ein char als 8 Bit geschrieben wird. Mit
strikt konformem C ist das nicht zu machen[*].

[*] Es koennte moeglich sein, Code zu schreiben, der ein vielfaches von 8
und CHAR_BIT als Puffer benutzt, in den man die Daten CHAR_BIT-weise
hineinpackt. Aber das ist ein gruseliger Gedanke.
Helmut Schellong
2018-08-25 18:32:47 UTC
Permalink
Post by Rainer Weikusat
Man hat mir mal gesagt, ich wuerde mich "nichtlinear" ausdruecken, und
das stimmt wohl. Dumme Leute versuchen gelegentlich, dass durch
zusammenhangsloses, lineares Gefasel zu imitieren, und noch duemmere
fallen sogar darauf herein (an einem anderen Ort so geschehen).
Gehoert hier aber nicht her.
Das ist einfach polnische und umgekehrte polnische Notation im Wechsel.

Gehört hier aber nicht her.
Gehört aber nicht hier her.
Gehört aber hier nicht her.
Gehört hier her aber nicht.
Hier her gehört das aber nicht.
Hier gehört das aber nicht her.
Aber nicht hier her gehört das.
Nicht hier her gehört das aber.
Aber gehört nicht hier her.
Nicht aber hier her gehört das.
Ein großer Pimmel hier her gehört.
Eine feuchte Aussprache du hast.
...

Einfach im Wechsel anwenden, und fast jeder in den Wahnsinn gerät.
--
Mit freundlichen Grüßen
Helmut Schellong ***@schellong.biz
www.schellong.de www.schellong.com www.schellong.biz
http://www.schellong.de/c.htm
Rainer Weikusat
2018-08-26 12:00:04 UTC
Permalink
Post by Helmut Schellong
Post by Rainer Weikusat
Man hat mir mal gesagt, ich wuerde mich "nichtlinear" ausdruecken, und
das stimmt wohl. Dumme Leute versuchen gelegentlich, dass durch
zusammenhangsloses, lineares Gefasel zu imitieren, und noch duemmere
fallen sogar darauf herein (an einem anderen Ort so geschehen).
Gehoert hier aber nicht her.
Das ist einfach polnische und umgekehrte polnische Notation im
Wechsel.
Man koennte es einen verkappten Binnenreim nennen.
Post by Helmut Schellong
Gehört hier aber nicht her.
Gehört aber nicht hier her.
Die zwei halte ich fuer inhaltsgleich.
Post by Helmut Schellong
Gehört aber hier nicht her.
Das hier auch, aber es ist seltsam.
Post by Helmut Schellong
Gehört hier her aber nicht.
Und das ist Quatsch.

Sozusagen ein Gehtschlechtorgan.
Peter J. Holzer
2018-08-26 09:07:38 UTC
Permalink
Post by Rainer Weikusat
Post by Peter J. Holzer
Post by Rainer Weikusat
Man kann das aber auch, wie im Beispiel angedeutet, in einen
Speicherpuffer serialisieren und den mit einem einzigen $quasifumbel
schreiben.
Natürlich. Das ist genau das gleiche.
Drei unterschiedliche Dinge koennen nicht "genau das gleiche" sein.
Sie können im Bezug auf den Aspekt, den wir hier diskutieren, das
gleiche tun. Natürlich tun

putc((u >> 8) & 0xFF); putc((u >> 0) & 0xFF);

und

*p++ = (u >> 8) & 0xFF; *p++ = (u >> 0) & 0xFF;

nicht in allen Aspekten das gleiche. Das erste schreibt auf einen
Stream, das zweite in ein Array. Aber was wir hier diskutieren, ist eine
endian-unabhängige Serialisierung. Und die ist in beiden Fällen gegeben
und sie ist identisch (nur steht halt das Ergebnis woanders).

Auf dem Abstraktionslevel, auf dem ich das betrachte, tun die also genau
das gleiche.

Ein

fwrite(&u, sizeof(u), 1, fp);

hingegen tut etwas anderes. Nur unter ganz bestimmten Umständen liefert
es das gleiche Ergebnis.
Post by Rainer Weikusat
Dh man muss sich nicht zwischen "Endianabhaengig" und "Ruft 500x
fwrite auf" entscheiden: Anstatt die Daten via stdio in einen in der
Bibliothek versteckten Puffer zu kippen, durch dessen Benutzung man
sich all moeglichen anderen Nachteile einhandelt, wie zB "ein winziges
TCP-Segment mit einem drittel eines Protokollheaders wird jetzt
gesendet, die restlichen zwei Drittel plus die Nutzdaten plus zwei
Drittel des naechsten Headers in 15 Minuten und der Rest nie, weil
jemand den Computer ploetzlich ausgeschaltet hat", kann man eine
komplette Nachricht in einem Speicherpuffer endian-unabgaengig
zusammenbauen und zu einem bestimmten Zeitpunk als ganzes verschicken.
Sicher. Würde ich in der Praxis sogar fast sicher so machen (kommt auf den
Anwendungszweck an, aber tendiere normalerweise dazu, Serialisierung und
I/O zu trennen, weil es einfacher zu testen ist). Ist aber für mein
Argument uninteressant. Und ich wollte das Argument nicht durch einen
zusätzlichen Layer verkomplizieren. Ich hätte mir denken können, dass
genau das dann zu Irritationen führt :-(.
Post by Rainer Weikusat
Post by Peter J. Holzer
In diesem Absatz schreibe ich davon, dass ein File mit einem definierten
Fileformat auf einem System mit mehr als 8 Bits/char gelesen wird. In
diesem Fall ist es völlig irrelevant, welche char-Größe das erzeugende
System hatte (und ob die Software überhaupt in C oder sonst einer
Sprache mit einem Datentyp "char" geschrieben war). Alles, was zählt,
ist, dass das File selbst der Spezifikation entspricht.
Das tut es aber nur, wenn ein char als 8 Bit geschrieben wird. Mit
strikt konformem C ist das nicht zu machen[*].
[*] Es koennte moeglich sein, Code zu schreiben, der ein vielfaches von 8
und CHAR_BIT als Puffer benutzt, in den man die Daten CHAR_BIT-weise
hineinpackt. Aber das ist ein gruseliger Gedanke.
Ein char hat mindestens 8 Bit. Man kann also portabel Werte zwischen 0
und 255 schreiben. Das ist das, was für praktisch alle portablen
Fileformate notwendig ist. Auf einer Maschine mit CHAR_BIT > 8 könnte
man einen größeren Wertebereich schreiben, aber man muss nicht. Jede
Speichereinheit auf Disk enthält halt dann auch nur Werte von 0 bis 255,
auch wenn mehr möglich wäre. Wenn CHAR_BIT wenig größer als 8 ist (9 ist
das generische Beispiel, weil das mal relativ verbreitet war) wird man
wohl mit dem Verschnitt leben (so wie man ja bei ASCII auch mit dem
Verschnitt lebt, und nicht versucht 8 7-Bit-ASCII-Werte in 7 Bytes
hineinzuquetschen). Wenn CHAR_BIT deutlich größer ist (z.B. 32) gibt es
wahrscheinlich einen "gepackten" I/O-Modus, der jeweils mehrere Octets
in eine Speichereinheit packt. Den muss man halt dann beim fopen
verwenden, im Rest des Codes kümmert man sich nicht mehr darum,
(Ich vermute, dass dieser Fall aber ohnehin akademisch ist: Gibt es
hosted-Implementationen mit CHAR_BIT == 32? Ich habe mal für einen m96k
Software geschrieben, aber das war nicht hosted.) Beim Transfer von
Files von/zu solchen Maschinen muss eventuell die Transfersoftware was
machen, aber das ist nicht Aufgabe des Anwendungsprogrammierers.

hp
--
_ | Peter J. Holzer | Fluch der elektronischen Textverarbeitung:
|_|_) | | Man feilt solange an seinen Text um, bis
| | | ***@hjp.at | die Satzbestandteile des Satzes nicht mehr
__/ | http://www.hjp.at/ | zusammenpaßt. -- Ralph Babel
Rainer Weikusat
2018-08-26 20:56:37 UTC
Permalink
[...]
Post by Peter J. Holzer
Post by Rainer Weikusat
Das tut es aber nur, wenn ein char als 8 Bit geschrieben wird. Mit
strikt konformem C ist das nicht zu machen[*].
[*] Es koennte moeglich sein, Code zu schreiben, der ein vielfaches von 8
und CHAR_BIT als Puffer benutzt, in den man die Daten CHAR_BIT-weise
hineinpackt. Aber das ist ein gruseliger Gedanke.
Ein char hat mindestens 8 Bit. Man kann also portabel Werte zwischen 0
und 255 schreiben. Das ist das, was für praktisch alle portablen
Fileformate notwendig ist. Auf einer Maschine mit CHAR_BIT > 8 könnte
man einen größeren Wertebereich schreiben, aber man muss nicht. Jede
Speichereinheit auf Disk enthält halt dann auch nur Werte von 0 bis 255,
auch wenn mehr möglich wäre. Wenn CHAR_BIT wenig größer als 8 ist (9 ist
das generische Beispiel, weil das mal relativ verbreitet war) wird man
wohl mit dem Verschnitt leben
Man kann nicht "portabel Werte zwischen 0 und 255 schreiben", denn Wert
ist ein abstraktes Konzept. Man kann unsigned chars schreiben, die einen
Wert zwischen 0 und 255 kodiert repraesentieren, dh als Folge von
sovielen Wertbits, wie es der Breite des Typs entspricht. Fuer den
"CHAR_BIT == 9"-Beispielfall bekommt man je ein 1-bit Loch zwischen
8 benutzen Wertbits.

[...]
Post by Peter J. Holzer
Beim Transfer von Files von/zu solchen Maschinen muss eventuell die
Transfersoftware was machen, aber das ist nicht Aufgabe des
Anwendungsprogrammierers.
Folglicherweise muss "irgendwelche Software", der Quell- und
Zielcharbreite bekannt sind, die perforierte Ausgabe entsprechend
umpacken, um entweder die Loecher zu eliminieren oder sie geeignet zu
vergroessern, damit eine solche Datei von einer anderen C-Implementierung
gelesen werden kann, uebrigens eine Eigenschaft, die die C-Norm nicht
fordert:

A binary stream is an ordered sequence of characters that can
transparently record internal data. Data read in from a binary
stream shall compare equal to the data that were earlier written
out to that stream, under the same implementation.
7.19.2|3

Verwendet man uint8_t gibt es zwei Moeglichkeiten:

1) Die Ausgabe entspricht der Spezifikation.
2) Der Code kann nicht uebersetzt werden

Bei char oder unsigned char kann der Code uebersetzt werden, aber die
Ausgabe entspricht moeglicherweise nicht der Spezifikation. PNG ist hier
ein "guenstig gewaehltes Beispiel" weil der entsprechende Text
8-Bit-Bytes implizit vorraussetzt.

Netzwerkprotokolle verdeutlichen das Problem eher, weil diese
ueblicherweise als Bitstreams definiert werden. ZB definiert RFC2408
einen ISAKMP-Header als

1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
! Initiator !
! Cookie !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
! Responder !
! Cookie !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
! Next Payload ! MjVer ! MnVer ! Exchange Type ! Flags !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
! Message ID !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
! Length !
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Trotz der seltsamen Numerierung (drei Zehner- eine Zweiergruppe) wird
hier implizit von einer Byte-Groesse, durch die sich 32 ohne Rest teilen
laesst, ausgegangen. Hier langt ein C-char noch nicht einmal mehr zum
Augenwischen: Software, die einen solchen Header erzeugen will, muss das
vorgeschriebene Bitmuster irgendwie in einen geeigneten Ganzzahltyp
hineinpfriemeln.

Falls CHAR_BIT == 9, muesste man hier eine 'padding'-Payload definieren,
mit der man ueberzaehilge Bits am Ende einer kodierten Nachricht
virtuell zum Verschwinden bringen kann.
Stefan Ram
2018-08-17 21:11:35 UTC
Permalink
Post by Thomas Koenig
Der Satz aus https://developer.ibm.com/linuxonpower/porting-guide/
"POWER uses the same byte order as x86 architectures so compiling
tools and working on POWER is almost exactly like other setups."
sagt eine ganze Menge über gegenwärtige Praktiken der Software-
Entwicklung. Für sauber geschriebene Software sollte es keinen
Unterschied machen, ob der Prozessor big- oder little-endian
ist, und Power LE wurde extra eingeführt, damit diese unsaubere
Du meinst wahrscheinlich "nicht-portable".

Es ist unter gewissen Umständen vollkommen legitim,
nicht-portable Software für ein bestimmtes System zu
schreiben.
Post by Thomas Koenig
Software leichter portiert werden kann...)
Bonita Montero
2019-01-20 17:37:58 UTC
Permalink
Tjaaa, besser C++ nutzen! Hab da Strukturen für explizite big- und
little-endian Datentypen und die haben Operatoren mit denen deren
Content transparent in die Datentypen von <cstdint> gewandelt werden
können. Die Operatoren nutzen bei mir ggf. das Plattform-abhängige
Intrinsic für BSWAP. Performance-impact bei heutigen Compilern:
absolut gar keiner bzw. die Struktur packt der Compiler weil es
nur ein Datenmember hat das eine Register-typische Größe hat eben
in die Register.

Loading...