Hallo,
Ich hätte gern eine union bestehend aus einem byte array und einer
struct. Die Struct befülle ich zunächst mit Daten und bei versenden oder
emfang / auswerten möchte ich die daten im bytearray mithilfe der struct
auseinander klammüsern.
Was ist nun mein Problem?
Ich habe irgendwie im hinterkopf, dass es irgendwie compiler abhängig
sein könnte wie die bit reihenfolge, das alignment der structur
gespeichert wird. Das könnte bei der "wandlung" von struct zu byte array
problematisch werden.. vll doch nur ein bytearray nehmen und sich alles
zusammen shiften?
Die Bits stapelt der Compiler so zusammen, wie sie eintreffen. Wichtig
ist nur, das ganze PACKED zu machen, damit dir da kein Padding in die
Quere kommt. Und auf LE/BE achten, falls notwendig, da hilft auf dem
Cortex-M der REVSH.
Hallo Random,
Die Strukturen sind immer vielfache von 4bytes. Wenn das der Fall ist,
dann kann ich doch sicher immer auf das "pack" verzichten. Was meinst du
mit LE/BE?
Random .. schrieb:> Und auf LE/BE achten, falls notwendig, da hilft auf dem> Cortex-M der REVSH.
OP schrieb nix von einem Cortex-M.
Und ich habe bei 8-Bit Compilern schon gesehen das die Hersteller sich
nicht über Big oder Little Endian auf einer Plattform einig waren.
OP hätte schreiben sollen wo der Code überall laufen muss, davon hängt
ab wie aufwändig man die (De-) Serialisierung macht.
>> OP schrieb nix von einem Cortex-M.>> OP hätte schreiben sollen wo der Code überall laufen muss, davon hängt>> ab wie aufwändig man die (De-) Serialisierung macht.
Ich rede zunächste von einem atmega :)
unions structs bildfields schrieb:> Ich habe irgendwie im hinterkopf, dass es irgendwie compiler abhängig> sein könnte wie die bit reihenfolge, das alignment der structur> gespeichert wird.
Korrekt. In C++ ist es sogar komplett verboten. Siehe Serialisierung
und meine Bibliothek welche dieses Problem löst:
https://github.com/Erlkoenig90/uSer
Frickel schrieb:> Um mal über den Tellerrand zu schauen
Stimmt, aber das Hauptproblem von C und C++ ist hier, dass Fehler dieser
Art noch sehr oft falsch beigebracht werden, auch von Büchern und
Kursen. Allein die Tatsache dass hier im Forum bei solchen Fragen immer
sofort der Begriff "union" fällt spricht Bände...
Also
Keine union: stattdessen memcpy, byte-ptr oder einzeln aufdröseln
Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:
die Optionen und Eigenschaften raussuchen und konfigurieren
Per compiletime asserts absichern.
A. S. schrieb:> Keine union: stattdessen memcpy, byte-ptr oder einzeln aufdröseln
memcpy und Pointer sind genau so Plattform/Compiler abhängig.
A. S. schrieb:> Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:> die Optionen und Eigenschaften raussuchen und konfigurieren
Klingt ziemlich aufwendig, das für die Hunderte an Optionen und
Plattformen zu prüfen. Ich bevorzuge es so zu machen, wie der
Sprachstandard die Funktion garantiert, nämlich per bitweisen
Operationen. Ist letztlich auch nicht mehr Code als die Byte-Reihenfolge
nach einem memcpy zu korrigieren, wenn sie nicht passt.
Olaf schrieb:> Kannst du so machen
Ist immer noch Implementation defined und somit unportabel.
Niklas G. schrieb:> Allein die Tatsache dass hier im Forum bei solchen Fragen immer sofort> der Begriff "union" fällt spricht Bände...
A. S. schrieb:> Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:
warum ist der Durchlauf des ganzen struct-Speicherblockes per
char-pointer nicht allgemengültig und vom Compiler abhängig?
soviet union schrieb:> A. S. schrieb:>> Wenn memcpy oder byte-ptr, dann für genau den uC und Compiler:>> warum ist der Durchlauf des ganzen struct-Speicherblockes per> char-pointer nicht allgemengültig und vom Compiler abhängig?
Kurz: Weil der Standard es nicht vorschreibt.
Etwas länger: Der Standerd erlaubt den Zugriff auf die Bytes über einen
Char-Pointer, schreibt aber nicht vor was in den Bytes steht, oder
wieviele es davon gibt.
Olaf schrieb:> uint8_t Data12; // 46> uint8_t Data13; // 47> uint8_t Data14; // 48
Ok, dann ist schon das hier UB oder wie? Wenn char > 8 Bytes ist?
Welchen Sinn hat dann uint8_t-Typ? Da verlässt man sich doch auch auf 8
bits.
Nach der Logik sollte dann uint8_t ein Synonym für uint_least8_t sein.
Und kein typedef für unsigned char.
So, ich habe mal "gegoogelt".
Die Datentypen uint8_t / int8_t kann es auf einer Platform zwar geben,
muss es aber nicht. Wenn es sie gibt, dann ist sizeof(char) == 1 Byte.
Tja, damit ist jeder Code, der uint8_t benutzt "implemantation defined".
Und nicht nur irgendwelche Zeigerzugriffe. Das Problem entsteht also
viel früher.
Es ist also nicht sehr sinnvoll uint8_t nutzen und beim char*-Zeiger
den Zeigefinger erheben.
So sehe ich das.
soviet union schrieb:> Tja, damit ist jeder Code, der uint8_t benutzt "implemantation defined".> Und nicht nur irgendwelche Zeigerzugriffe. Das Problem entsteht also> viel früher.> Es ist also nicht sehr sinnvoll uint8_t nutzen und beim char*-Zeiger> den Zeigefinger erheben.> So sehe ich das.
Da hast Du mh und mich falsch verstanden: char oder byte-ptr sind immer
OK, um eine Struktur zu übertragen. Keine Einschränkung. Man muss
Compiler und Plattform prüfen, weil:
mh schrieb:> Der Standerd erlaubt den Zugriff auf die Bytes über einen Char-Pointer,> schreibt aber nicht vor was in den Bytes steht, oder wieviele es davon> gibt
Konkret: little oder big endian, padding Bytes, bitstrukturen, 2er
Komplement, Repräsentation von floats, 8 oder 9 Bit pro char/Byte,
Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die
komplette Struktur in Bytes zu zerlegen.
> Ist immer noch Implementation defined und somit unportabel.
Klar, ist es muss. Muss man pruefen. Man koennte Pech haben.
In aller Regel klappt es aber. Zumal ich in den Datenbloecken die ich
damit verschicke auch eine CRC32 drin habe.
Im Vergleich zu den inkompetenten Murks den ich hier oft im
Hardwarebereich sehe ich das euer geringstes Problem, glaubt mir. .-)
Olaf
Olaf schrieb:> In aller Regel klappt es aber
Bei wie viel Prozent der Plattformen reicht es? Zwischen AVR und ARM ist
es schon nicht übertragbar. Und das "packed" Attribut ist Gift für die
Performance auf 32 bit Plattformen.
Olaf schrieb:> Zumal ich in den Datenbloecken die ich damit verschicke auch eine CRC32> drin habe.
Die bringt überhaupt nichts wenn sie über die einzelnen Bytes berechnet
ist.
Olaf schrieb:> Im Vergleich zu den inkompetenten Murks den ich hier oft im> Hardwarebereich sehe ich das euer geringstes Problem, glaubt mir. .-)
Und das ist ein Argument, es nicht richtig zu machen, obwohl es doch so
einfach wäre, und partout an "union" festzuhalten?
Niklas Gürtler schrieb:> A. S. schrieb:>> Keine union: stattdessen memcpy, byte-ptr oder einzeln aufdröseln>> memcpy und Pointer sind genau so Plattform/Compiler abhängig.
Aber es ist weniger umständlich und missbraucht nicht ein Sprachfeature,
das dafür eigentlich gar nicht gedacht war.
soviet union schrieb:> So, ich habe mal "gegoogelt".>> Die Datentypen uint8_t / int8_t kann es auf einer Platform zwar geben,> muss es aber nicht. Wenn es sie gibt, dann ist sizeof(char) == 1 Byte.
sizeof(char) ist immer 1 Byte, egal ob es uint8_t gibt oder nicht.
Ansonsten soweit richtig.
> Tja, damit ist jeder Code, der uint8_t benutzt "implemantation defined".
Wenn man davon ausgeht, Datentypen mit einer ganz bestimmten Größe
vorzufinden, ist das immer implementation-defined.
> Es ist also nicht sehr sinnvoll uint8_t nutzen und beim char*-Zeiger> den Zeigefinger erheben.
Es gibt da schon einen Unterschied. char muss nicht exakt 8 Bit breit
sein, uint8_t aber schon. Wenn ich also auf einer Plattform, die keinen
8-Bit-Typen kennt, char verwende und dabei voraussetze, dass der exakt 8
Bit breit ist, kommt dabei ein Programm raus, das Blödsinn macht. Nehme
ich uint8_t, dann bricht der Compiler mit Fehler ab, da der benötigte
Typ nicht existiert. Außerdem dokumentiert es den Code besser, wenn
durch uint8_t klar gemacht wird, dass hier ganz speziell ein exakt 8 Bit
breiter Datentyp gefordert ist.
A. S. schrieb:> Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die> komplette Struktur in Bytes zu zerlegen.
Allerdings nur per memcpy, nicht per einfachem Pointer-cast.
Niklas Gürtler schrieb:> Olaf schrieb:>> In aller Regel klappt es aber>> Bei wie viel Prozent der Plattformen reicht es? Zwischen AVR und ARM ist> es schon nicht übertragbar. Und das "packed" Attribut ist Gift für die> Performance auf 32 bit Plattformen.
Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem
Alignment gar nicht unterstützt wird und das Programm z.B. mit einer
Hardware-Exception abgebrochen wird.
Rolf M. schrieb:> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer> Hardware-Exception abgebrochen wird.
Dafür gibt es im GCC das "-munaligned-access" -Flag - ist es gesetzt,
wird angenommen die Hardware kann solche unaligned Zugriffe, und wenn
sie es doch nicht kann, gibt es eine Exception. Ist das Flag nicht
gesetzt, werden die Zugriffe über einzelne Byte-Zugriffe umgesetzt, die
dann besonders langsam sind. So oder so ist "packed" langsam und keine
echte Lösung.
Niklas G. schrieb:> Rolf M. schrieb:>> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem>> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer>> Hardware-Exception abgebrochen wird.>> Dafür gibt es im GCC das "-munaligned-access" -Flag - ist es gesetzt,> wird angenommen die Hardware kann solche unaligned Zugriffe, und wenn> sie es doch nicht kann, gibt es eine Exception. Ist das Flag /nicht/> gesetzt, werden die Zugriffe über einzelne Byte-Zugriffe umgesetzt, die> dann besonders langsam sind. So oder so ist "packed" langsam und keine> echte Lösung.
Offtopic:
An jener Stelle sei erwähnt dass dieses Flag automatisch gesetzt wird
wenn die entsprechende Architektur übergeben wird, sprich etwa
-march=armv7e-m. Man muss sich nicht selbst "händisch" darum kümmern ob
die Architektur unaligned Zugriffe unterstützt oder nicht.
> Bei wie viel Prozent der Plattformen reicht es?
Hab ich bisher zwischen diversen ARMs, SH2, 68000, PC(Qt), 68332 und
M16C erfolgreich genutzt. Zu AVR kann ich nichts sagen weil ich von dem
Murks schon seit 20Jahren Weg bin.
> Die bringt überhaupt nichts wenn sie über die einzelnen Bytes berechnet> ist.
Und du wirst gleich sterben weil ich gerade die Eingebung hatte das du
nur von Pizza und Cola lebst. Wieso definierst du etwas dummes und
leitest daraus daraus eine Voraussage fuer deine Umwelt ab?
> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer> Hardware-Exception abgebrochen wird.
Hier noch nicht passiert. Aber ist ja super. Merkt man dann ja gleich
beim allerersten Test oder?
Wie hier ja ausreichend dargelegt, es mag nicht perfekt sein, aber in
>90% der Faelle kein Problem. Man muss vielleicht also mal sein Hirn
einschalten. Ansonsten ist es euer geringstes Problem. Einfach mal
ausprobieren...
Olaf
Olaf schrieb:> Hab ich bisher zwischen diversen ARMs, SH2, 68000, PC(Qt), 68332 und> M16C erfolgreich genutzt.
Hast du auch geprüft wie viel langsamer die "packed" Zugriffe sind?
Olaf schrieb:> Wieso definierst du etwas dummes
Wo habe ich eine Definition gegeben?
Olaf schrieb:> Merkt man dann ja gleich> beim allerersten Test oder?
Nö. Bei manchen Plattformen (Cortex-M0?) passiert gar nichts, es wird
nur Murks gelesen. Du merkst dann irgendwann dass du falsche Daten hast.
Olaf schrieb:> Man muss vielleicht also mal sein Hirn> einschalten. Ansonsten ist es euer geringstes Problem. Einfach mal> ausprobieren...
Auch ein Problem von C - es erzeugt eine Art kognitive Dissonanz. In C
gibt es eine Reihe an Dingen wie "union", die man zweckentfremden kann
und die dann den Anschein geben, zu funktionieren. Das installiert sich
dann im Gehirn als "SO macht man das", auch wenn es falsch ist. Wenn man
dann darauf hingewiesen wird, dass es problematisch ist, und dass die
korrekte Lösung eigentlich ganz einfach wäre, kommen Beleidigungen und
"in 90% der Fälle funktioniert es" um auf jeden Fall die alt-bekannten
falschen Wege zu rechtfertigen.
Olaf schrieb:>> Je nach Plattform kann es auch sein, dass ein Zigriff mit falschem>> Alignment gar nicht unterstützt wird und das Programm z.B. mit einer>> Hardware-Exception abgebrochen wird.>> Hier noch nicht passiert. Aber ist ja super. Merkt man dann ja gleich> beim allerersten Test oder?
Ja, sofern tatsächlich eine Exception kommt und nicht wie z.B. oben von
Vincent erklärt der Compiler jeden Zugriff im Hintergrund automatisch
über Zusammenstückelung einzelner Bytes umsetzt.
> Hast du auch geprüft wie viel langsamer die "packed" Zugriffe sind?
Noe, hat mich nicht interessiert. Ich hab nur noch Anwendungen wo der
Controller 10-1000x schneller ist als man es braucht und lasse ihn
meistens nur noch mit wenigen Mhz laufen. Und hey, mit sowas sollen
Daten uebertragen werden. Wenige Bytes pro Minute. Du machst dir Sorgen
um Softwareeffizienz? Schau dir mal durchschnittliche Handy oder
PC-Software an. Da gibt es Handlungsbedarf!
> Nö. Bei manchen Plattformen (Cortex-M0?) passiert gar nichts, es wird> nur Murks gelesen. Du merkst dann irgendwann dass du falsche Daten hast.
Ach? Du merkst das also dann gleich beim allerersten Test deine Software
oder? Ich meine du testest doch eine Funktion wenn du sie schreibst? Du
merkst doch sicher an der Pruefsumme wenn falsche Daten kommen? Du
staunst doch bestimmt wenn der andere Mikrocontroller der die Daten
empfaengt nicht das macht was er soll?
> Wenn man dann darauf hingewiesen wird, dass es problematisch ist,
Du wiederholst dich...
Olaf
Rolf M. schrieb:> A. S. schrieb:>> Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die>> komplette Struktur in Bytes zu zerlegen.>> Allerdings nur per memcpy, nicht per einfachem Pointer-cast.
Mit einem Char-Pointer auf die Bytes der Struktur zugreifen ist erlaubt.
mh schrieb:> Rolf M. schrieb:>> A. S. schrieb:>>> Es ist aber explizit erlaubt, mit einem byteptr und sizeof(Struktur) die>>> komplette Struktur in Bytes zu zerlegen.>>>> Allerdings nur per memcpy, nicht per einfachem Pointer-cast.>> Mit einem Char-Pointer auf die Bytes der Struktur zugreifen ist erlaubt.
Sie per memcpy in ein Array aus unsigned char kopieren und dann auf
dieses zugreifen, ist erlaubt. Das wird ausdrücklich genannt:
"Values stored in non-bit-field objects of any other object type consist
of n×CHAR_BIT bits, where n is the size of an object of that type, in
bytes. The value may be copied into an object of type unsigned
char[n](e.g., by memcpy); the resulting set of bytes is called the
object representation of the value."
Ich wüsste keine Passage, die es explizit erlaubt, auch direkt auf die
Bytes des Objekts als Array aus char zuzugreifen.
Rolf M. schrieb:> Ich wüsste keine Passage, die es explizit erlaubt, auch direkt auf die> Bytes des Objekts als Array aus char zuzugreifen.
Die Passage weiß ich gerade nicht, aber es ist definitiv erlaubt. Genau
das macht memcpy auch, und memcpy ist an sich nichts besonderes, das
kann man sich auch selbst implementieren über char-Pointer. Die
C-Library hat nur meistens eine für die Plattform handoptimierte
Version.
Ist aber egal, denn das Ergebnis ist natürlich auch implementation
defined.
Für Datenübertragung zwischen unterschiedlichen Systemen mit den ganzen
Little/big endian, Bitfelder und Enum Problemen definiere ich die
möglichen Nachrichten formal mit Hilfe von ASN.1
https://de.wikipedia.org/wiki/Abstract_Syntax_Notation_One
Für fast jede gebräuchliche Programmiersprache gibt es Codegeneratoren
um die verschiedenen Encodings zu erzeugen. Ich bevorzuge dabei
unaligned Packet Encoding Rules (uPER) da das sehr kompakt ist.
Als Generator verwende ich
https://github.com/ttsiodras/asn1scc
von der ESA aber k.A. ob der für AVR geeignet ist, habe den bisher nur
auf Cortex-A, NIOS und X86-64 benutzt.
Eine Alternative zu ASN.1 sind z.B. Google Protocol Buffer
@Niklas
Auf Deiner Serialisierungs-Seite
(https://www.mikrocontroller.net/articles/Serialisierung) schreibts Du:
"je nachdem ob die Implementation eine Sign-Extension vornimmt"
Hast Du da weitergehende Informationen? Ich habe nichts zu
Implementierungsabhängigkeit von Sign-Extensions finden können?
Darüberhinaus ist mir aber auch schon der Begriff "Sign-Extension" nicht
ganz klar. In Deinem Beispiel steht '(int16_t) -32768'. Warum sollte
irgendein Compiler da was erweitern, das lässt sich doch mit den 16 Bit
darstellen:
10000000 00000000
Oder bedeutet Sign-Extension, dass der Compiler/die Plattform aus
irgendwelchen Gründen evtl. intern gar keine 16-Bit-Integer kennt und
intern z.B. immer mit 32 Bit rechnet?
Niklas G. schrieb:> Rolf M. schrieb:>> Ich wüsste keine Passage, die es explizit erlaubt, auch direkt auf die>> Bytes des Objekts als Array aus char zuzugreifen.>> Die Passage weiß ich gerade nicht, aber es ist definitiv erlaubt. Genau> das macht memcpy auch, und memcpy ist an sich nichts besonderes, das> kann man sich auch selbst implementieren über char-Pointer. Die> C-Library hat nur meistens eine für die Plattform handoptimierte> Version.>> Ist aber egal, denn das Ergebnis ist natürlich auch implementation> defined.
Ich habe noch einen Nachtrag:
1
5.1.2.1 Freestanding environment
2
[...]
3
Any library facilities available to a freestanding program, other than the minimal set required by clause 4, are implementation-defined.
4
[...]
und
1
4.6
2
[...]
3
A conforming freestanding implementation shall accept any strictly conforming program in which the use of the features specified in the library clause (clause 7) is confined to the contents of the standard headers <float.h>, <iso646.h>, <limits.h>, <stdalign.h>, <stdarg.h>, <stdbool.h>, <stddef.h>, <stdint.h>, and <stdnoreturn.h>. A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any strictly conforming program.
Alles aus dem c11 draft (n1570)
Man könnte also sagen, ein Programm das memcpy nutzt ist
implementation-definded, da string.h nicht aufgelistet ist.
mh schrieb:> Man könnte also sagen, ein Programm das memcpy nutzt ist> implementation-definded, da string.h nicht aufgelistet ist.
Aber nur wenn es sich um eine freestanding implementation handelt.
Soweit ich weiß, sind avr-gcc und ein Großteil der ARM-Toolchains als
hosted implementation ausgelegt.
Stefan schrieb:> Hast Du da weitergehende Informationen? Ich habe nichts zu> Implementierungsabhängigkeit von Sign-Extensions finden können?https://stackoverflow.com/a/7636 und im C-Standard¹, S. 85:
The result of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has
an unsigned type or if E1 has a signed type and a nonnegative value, the
value of the result is the integral part of the quotient of E1 / 2^E2.
If E1 has a signed type and a negative value, the resulting value is
implementation-defined.
Bei manchen Plattformen macht der Compiler halt eine Sign-Extension, und
bei anderen nicht. Das hängt typischerweise davon ab, was der Prozessor
am Besten kann.
Stefan schrieb:> das lässt sich doch mit den 16 Bit> darstellen:> 10000000 00000000
Richtig. Und wenn man das als signed Integer in C nach rechts shiftet,
wird die 1 entweder nur verschoben oder kopiert. Diese Zahl 3 bits
nach rechts shiften kann als Ergebnis 00010000 00000000 oder auch
11110000 00000000 haben. Der C-Standard legt das nicht fest.
Stefan schrieb:> Oder bedeutet Sign-Extension, dass der Compiler/die Plattform aus> irgendwelchen Gründen evtl. intern gar keine 16-Bit-Integer kennt und> intern z.B. immer mit 32 Bit rechnet?
Nö, das ist nochmal was anderes.
1: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
Rolf M. schrieb:> Aber nur wenn es sich um eine freestanding implementation handelt.> Soweit ich weiß, sind avr-gcc und ein Großteil der ARM-Toolchains als> hosted implementation ausgelegt.
1
5.1.2.2.2 Program execution
2
In a hosted environment, a program may use all the functions, macros, type definitions, and objects described in the library clause (clause 7).
Man kann mit dem avr-gcc alles nutzen, was in "clause 7" steht?
Stefan schrieb:> In Deinem Beispiel steht '(int16_t) -32768'. Warum sollte> irgendein Compiler da was erweitern, das lässt sich doch mit den 16 Bit> darstellen
Auf einer Plattform mit 16bit 2er Komplement int, ist "-32768" ein long.
Das "-" ist kein Teil des Literals.
mh schrieb:> Rolf M. schrieb:>> Aber nur wenn es sich um eine freestanding implementation handelt.>> Soweit ich weiß, sind avr-gcc und ein Großteil der ARM-Toolchains als>> hosted implementation ausgelegt.> 5.1.2.2.2 Program execution> In a hosted environment, a program may use all the functions, macros,> type definitions, and objects described in the library clause (clause> 7).>> Man kann mit dem avr-gcc alles nutzen, was in "clause 7" steht?
Ob er (bzw. die avr-libc) das vollständig untestützt, kann ich nicht
sagen. Ich vermute mal, nicht. Aber zumindest wird gcc per Default im
"hosted"-Modus betrieben. Ohne den würde er keine Optimierung der
Standard-Funktionen wie eben gerade memcpy machen, da er nicht mehr
annehmen kann, dass sie sich gemäß Standard verhalten.
Niklas G. schrieb:> Richtig. Und wenn man das als signed Integer in C nach rechts shiftet,> wird die 1 entweder nur verschoben oder kopiert. Diese Zahl 3 bits> nach rechts shiften kann als Ergebnis 00010000 00000000 oder auch> 11110000 00000000 haben. Der C-Standard legt das nicht fest.
D.h. die Variante 11110000 00000000 ist mit Sign-Extension (weil die
Zweierkomplement-Zahl praktisch von links mit Einsen negativ erweitert
wurde) und 00010000 00000000 ohne, richtig?
Niklas G. schrieb:> If E1 has a signed type and a negative value, the resulting value is> implementation-defined.
Ist das für einen Standard nicht etwas dünn? Wo liest man denn da Deine
beiden o.g. Varianten heraus (Sign-Extension wird ja auch überhaupt
nicht erwähnt)?
Das ist übrigens auf Seite 95 statt 85 (falls noch jemand anderes
nachschauen will)
mh schrieb:> Auf einer Plattform mit 16bit 2er Komplement int, ist "-32768" ein long.> Das "-" ist kein Teil des Literals.
Habe ich jetzt nicht verstanden. Also ist -32768 nicht 10000000 00000000
als Zweierkomplement?
Stefan schrieb:> D.h. die Variante 11110000 00000000 ist mit Sign-Extension (weil die> Zweierkomplement-Zahl praktisch von links mit Einsen negativ erweitert> wurde) und 00010000 00000000 ohne, richtig?
Ja. Sign Extension bedeutet praktisch dass man das ganz linke Bit (das
Sign Bit) kopiert.
Stefan schrieb:> Ist das für einen Standard nicht etwas dünn?
Die C- und C++-Standards sind halt so definiert, dass die Sprache auf
einer größtmöglichen Anzahl an Plattformen effizient lauffähig ist.
Daher werden den Compilern viele Freiheiten gelassen. Es wird ja nicht
einmal gefordert, dass 8-Bit-Bytes zur Verfügung stehen, denn char kann
auch größer sein. Sprachen mit sehr fixen Regeln wie Java sind auf
exotischeren Plattformen kaum lauffähig; auf einem DSP der nur
36bit-Integer kennt müsste Java jede einzelne "short" oder "int"
Operation aufwendig emulieren. Selbst auf AMD64 passt Java nicht so
richtig, da Array-Größen & Indices 32bit sind. C und C++ haben da
hingegen keine Probleme, da wird size_t einfach als 64bit definiert.
Allerdings sind selbst diese Freiheiten nicht immer ausreichend; auf AVR
oder 8051 lässt sich C auch nicht 100% korrekt abbilden und man braucht
Krücken wie "PROGMEM" oder "__flash".
Stefan schrieb:> Wo liest man denn da Deine> beiden o.g. Varianten heraus (Sign-Extension wird ja auch überhaupt> nicht erwähnt)?
Die Varianten sind nicht vorgeschrieben. Die Plattform kann irgendwas
tun. Die genannten Varianten sind nur die m.W. tatsächlich
existierenden. Das 2er-Komplement ist ja auch nicht vorgegeben, die
Plattform kann etwas anderes nutzen.
Stefan schrieb:> Das ist übrigens auf Seite 95 statt 85 (falls noch jemand anderes> nachschauen will)
Unten auf der Seite steht 85. Es ist nur PDF-Seite 97 (!).
Stefan schrieb:> D.h. die Variante 11110000 00000000 ist mit Sign-Extension (weil die> Zweierkomplement-Zahl praktisch von links mit Einsen negativ erweitert> wurde) und 00010000 00000000 ohne, richtig?
Ja.
> Niklas G. schrieb:>> If E1 has a signed type and a negative value, the resulting value is>> implementation-defined.>> Ist das für einen Standard nicht etwas dünn?
Das Problem ist eben, dass nicht jede Hardware das direkt untersützt.
Der Standard lässt dem Compiler die Wahl, damit der keine unnötig
ineffizienten Verrenkungen anstellen muss, um ein vom Standard
vorgeschriebenes Verhalten umzusetzen. So etwas gibt es in C an vielen
Stellen, damit eine sinnvolle Implementation der Sprache für nahezu jede
erdenkliche Plattform möglich ist.
> Wo liest man denn da Deine beiden o.g. Varianten heraus (Sign-Extension> wird ja auch überhaupt nicht erwähnt)?
implementation-defined heißt nur, dass der Compiler es definieren muss.
Mit und ohne Sign-Extension sind halt in der Praxis die gängigsten
Varianten. Im Prinzip darf der Compiler es aber machen, wie er will,
solange das Verhalten für positive Werte korrekt ist und für negative
Werte reproduzierbar und im Handbuch des Compilers dokumentiert ist.
> mh schrieb:>> Auf einer Plattform mit 16bit 2er Komplement int, ist "-32768" ein long.>> Das "-" ist kein Teil des Literals.> Habe ich jetzt nicht verstanden.
Es wird zuerst nur die 32768 betrachtet. Die passt nicht in einen
16-Bit-int, also muss ein long draus gemacht werden. Erst danach wird
der Wert negiert. Da würde er zwar in den int passen, aber es ist schon
zu spät. Er ist bereits ein long.
Rolf M. schrieb:> Es wird zuerst nur die 32768 betrachtet. Die passt nicht in einen> 16-Bit-int, also muss ein long draus gemacht werden. Erst danach wird> der Wert negiert. Da würde er zwar in den int passen, aber es ist schon> zu spät. Er ist bereits ein long.
Also macht der Compiler folgendes (im Folgenden wird immer angenommen,
der Compiler arbeitet mit der Zweierkomplement-Darstellung):
1. 32768 wird als 32 Bit behandelt (weil bei 16 Bit ein Überlauf
aufträte):
00000000 00000000 10000000 00000000
2. -32768 wird als 32-Bit-Zweierkomplement geschrieben:
11111111 11111111 10000000 00000000
3. Cast mit (int16_t)
Was bewirkt das jetzt? Werden einfach die höherwertigen 16 Bit
abgeschnitten? Dann hätte ich ja wieder das gewünschte Ergebnis.
Aber hat das überhaupt was mit dem Thema 'implementation-defined
Sign-Extension' zu tun?
Angenommen die Zahl wäre -20000 statt -32768:
'((int16_t) -20000) >> 3)'
Der Audruck '(int16_t) -20000' ergibt (ohne dass irgendwelche Überläufe
aufteten) als Zweierkomplement
10110001 11100000
Wenn ich das Zitat aus dem Standard und die Aussagen von Niklas richtig
deute, tritt das Problem 'implementation-defined Sign-Extension' ja
jetzt trotzdem auf (obwohl vorher kein Überlauf auftrat), nämlich
einfach immer bei Bit-Shifting mit Signed-Integer, denn ja nach
Implementierung kann die Verschiebung um 3 Bits nach rechts
(erfahrungsgemäß) diese beiden Ergebnisse erzeugen:
11110110 00111100 (Sign-Extension, von links mit Einsen auffüllen)
00010110 00111100 (keine Sign-Extension, von links mit Nullen
auffüllen)
Stefan schrieb:> Aber hat das überhaupt was mit dem Thema 'implementation-defined
-32768 ist negativ :) Das hatte ich nur als besonders prägnantes
Beispiel (-1 vs 1) gewählt. Das Problem besteht bei allen negativen
Zahlen.
Das wiederum hat auch wenig mit dem Thread zu tun, denn die ganze
Signed-Right-Shift-Problematik war auch nur ein Beispiel für
implementation defined behaviour.
Stefan schrieb:> Also macht der Compiler folgendes
Ja.
> Was bewirkt das jetzt? Werden einfach die höherwertigen 16 Bit> abgeschnitten? Dann hätte ich ja wieder das gewünschte Ergebnis.
Korrekt.
> Aber hat das überhaupt was mit dem Thema 'implementation-defined> Sign-Extension' zu tun?
Nein.
> Angenommen die Zahl wäre -20000 statt -32768:> '((int16_t) -20000) >> 3)'> Der Audruck '(int16_t) -20000' ergibt (ohne dass irgendwelche Überläufe> aufteten) als Zweierkomplement> 10110001 11100000
Hier ist kein Cast nötig, da 20000 in einen int16_t passt.
> Wenn ich das Zitat aus dem Standard und die Aussagen von Niklas richtig> deute, tritt das Problem 'implementation-defined Sign-Extension' ja> jetzt trotzdem auf (obwohl vorher kein Überlauf auftrat), nämlich> einfach immer bei Bit-Shifting mit Signed-Integer, denn ja nach> Implementierung kann die Verschiebung um 3 Bits nach rechts> (erfahrungsgemäß) diese beiden Ergebnisse erzeugen:> 11110110 00111100 (Sign-Extension, von links mit Einsen auffüllen)> 00010110 00111100 (keine Sign-Extension, von links mit Nullen> auffüllen)
Ja, richtig.