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?
Beitrag #6385397 wurde von einem Moderator gelöscht.
Beitrag #6385402 wurde von einem Moderator gelöscht.
Beitrag #6385408 wurde von einem Moderator gelöscht.
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.
Beitrag #6385415 wurde von einem Moderator gelöscht.
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 :)
Beitrag #6385461 wurde von einem Moderator gelöscht.
Beitrag #6385466 wurde von einem Moderator gelöscht.
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
Beitrag #6385500 wurde von einem Moderator gelöscht.
Beitrag #6385518 wurde von einem Moderator gelöscht.
Um mal über den Tellerrand zu schauen: Andere Sprachen als C/CPP haben das besser, oder zumindest anders oder noch besser, eindeutiger gelöst um von vorn herein portabel zu sein. Und das sogar Compiler unabhängig ;) Beitrag "[Ada] Record Representation Clauses - Wert eines "Records" in einem Zug füllen?"
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.
Kannst du so machen: struct DataTypeA { uint16_t Data01; // 2 uint16_t Data02; // 4 uint16_t Data03; // 6 uint16_t Data04; // 8 uint32_t Data05; // 12 uint32_t Data06; // 16 uint32_t Data07; // 20 double Data08; // 28 double Data09; // 36 double Data10; // 44 uint8_t Data11; // 45 uint8_t Data12; // 46 uint8_t Data13; // 47 uint8_t Data14; // 48 } _attribute_ ((packed)); union DataUnionA { struct DataTypeA DataA; uint8_t Byte[48]; }; Olaf
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.
6.6.7 im c11 draft (n1570) geguckt:
1 | An object shall have its stored value accessed only by an lvalue expression that has one of the following types: |
2 | — a type compatible with the effective type of the object, |
3 | — a qualified version of a type compatible with the effective type of the object, |
4 | — a type that is the signed or unsigned type corresponding to the effective type of the |
5 | object, |
6 | — a type that is the signed or unsigned type corresponding to a qualified version of the |
7 | effective type of the object, |
8 | — an aggregate or union type that includes one of the aforementioned types among its |
9 | members (including, recursively, a member of a subaggregate or contained union), or |
10 | — a character type. |
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 (!).
:
Bearbeitet durch User
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.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.