Hi, gibt es irdend welche Regeln, nach denen die einzelnen Felder eines structs mit dem darunterliegenden Speicher verbunden sind? Mal ausgehend von einer 8-Bit-MCU z.B. ATmega: Im RAM steht ab Addresse xx xx: 11 22 33 44 55 66 77 88 99 00 und ich habe ein struct mit typedef struct { unsigned char i; unsgined char j; unsigned long k; } myType; myType *t=xx; Kann ich jetzt sicher sein, daß t->i==0x11, t->j==0x22, und t->k==0x66554433 sein wird? Ich weiß, daß bei 16 und 32 Bit Controllern die einzelnen Elemente des structs auf 16 bzw. 32 Bit Grenzen gelegt werden. Aber würde das obige Beispiel auf mit einem 8-Bit Controller/Compiler immer funktionnieren, oder kann es sein, daß nach einer Optimierung so Dinge herauskommen wie t->i==66, t->j==55, t->k==44332211?
>Aber würde das obige >Beispiel auf mit einem 8-Bit Controller/Compiler immer funktionnieren, Nö, nicht unbedingt. Little Endian oder Big Endian könnte da noch eine Rolle spielen. Das hängt bei 8 Bit uC vom Compiler ab.
Bernd M. wrote: > Kann ich jetzt sicher sein, daß t->i==0x11, t->j==0x22, und Soweit theoretisch nein, praktisch ja. Nein, wenn jemand einen 8bit-Proz mit seltsamen Adressvorschriften erfindet. > t->k==0x66554433 sein wird? Hier aber auch 0x33445566 oder 0x44336655. Die Byteorder ist nicht festgelegt.
Also, solange ich den gleichen Compiler verwende, kann ich mich darauf verlassen, daß sich zumindest da nichts ändert, aber sobald ich (auch beim gleichen Mikrocontroller) den Compiler wechsele, ist alles unbestimmt. Richtig? Also ist der einzige zuverlässige Weg, wenn p mein pointer auf den Speicher wäre: t->i=*p++; t->j=*p++; t->k=(*p)+((*p+1)<<8)+((*p+2)<<16)+((*p+3)<<24);p+=4; Oder geht das noch einfacher?
Letztlich geht es mir darum structs zu serialisieren und dann auf einem anderen Mikrocontroller diesen Datenstrom wieder in ein Struct zu verwandeln.
Bernd M. wrote: > beim gleichen Mikrocontroller) den Compiler wechsele, ist alles > unbestimmt. Richtig? Nicht alles. Aber ... > Also ist der einzige zuverlässige Weg, wenn p mein pointer auf den > Speicher wäre: das ist eindeutig der sinnvollste Weg. Abgesehen von den fehlenden Casts (0x11<<24 ist undefiniert).
Programmieren kann man alles. Irgendwie;-) Fragt sich am Ende nur, wie effizient der Code ist.
Wenn es um Serialisierung geht, ist ersma zu spezifizieren, wie die Daten transportiert werden. ZB ein int als Bis + Bytes little Endian. Jede Seite ist dann dafür verantwortlich, über ein Interface das Zeug zu wandeln. Wenn du einfach den Speicherinhalt schicken willst, über das Layout aber nix sagen kannst, musst du eben ne andere Darstellung nehmen. zB als ASCII hin- und zrückwandeln. 0x11223344 wird also gesendet als "0x11223344", daran gibt's nix zu deuteln.
Was zuerst übertragen wird, kann ich noch entscheiden. Das ganze Konzept soll aber vor allem nicht zwischen 2 MCUs laufen, sondern in erster Linie auf einer einzigen. Die Daten sollen also in die Nachrichtenwarteschlange hinein serialisiert werden, wobei auch Nachrichten über den UART empfangen werden sollen, und anschließen, wenn die Nachricht abgearbeitet wird, wieder in Structs verwandelt werden. Das ganze wird in jedem Fall binär bleiben. Die Frage ist vielmehr, ob das obige Bit-Geschubse vielleicht eleganter geht. In Assembler würde ich auf einem 8-bitter nie 8/16/24 Bit verschieben, sonder das Byte gleich eine Addresse weiter speichern.
Bernd M. wrote: > Also ist der einzige zuverlässige Weg, wenn p mein pointer auf den > Speicher wäre: > > t->i=*p++; > t->j=*p++; > t->k=(*p)+((*p+1)<<8)+((*p+2)<<16)+((*p+3)<<24);p+=4; > > Oder geht das noch einfacher? wieso nicht einfach so? char* ptr = (char*) t; for (int i=0;i<sizeof(myType);i++) { send(*ptr); ptr++; } Also in einen Byteptr umcasten und einfach so lange die Bytes schicken, bis alles geschickt worden ist. Auf der anderen Seite andersrum, aber wenn das ein PC ist unbedingt _attribute_ ((packed)) oder sowas angeben, damit die Daten im struct nicht gepaddet werden. Du kannst das packed auch auf 8bit controllern verwenden ... MfG Thomas Pototschnig edit Wenn du das eh im Speicher hast, dann memcpy(t,p,sizeof(MyType)); und fertig ...
Bernd M. wrote: > geht. In Assembler würde ich auf einem 8-bitter nie 8/16/24 Bit > verschieben, sonder das Byte gleich eine Addresse weiter speichern. ... was auch der Compiler oft aus solchem Geschubse macht. Probier's aus.
> char* ptr = (char*) t; > > for (int i=0;i<sizeof(myType);i++) > { > send(*ptr); ptr++; > } > memcpy(t,p,sizeof(MyType)); Das ist Käse, wegen der später möglichen Endianness-Probleme. Heute hast du einen Little-Endian 8-Bit-Prozessor, und morgen soll der Code womöglich auch auf einem Big-Endian 32-Bit-Prozessor laufen und sich mit dem 8-Bitter verstehen. Für robusten Code sollte man die Bytes explizit ein- und auspacken. Jürgen PS: Wir haben gerade Spaß mit PPC (big endian) vs x86 (little endian).
Gibts dafür ne gute Bib? Oder hat jemand dafür ein wirklich gutes Beispiel? Ich hör immer nur structs sind böse, aber wirklich effizienten (8-bit µC!!!) Code konnte bisher keiner liefern.
Wer sagt das Structs böse sind. Sie machen Dein Programm einfach übersichtlicher. Klassen in C++ sind natürlich noch etwas komfortabler. Das Hauptproblem, weswegen ich diesen Thread überhaupt gepostet habe, war, daß ich das ganze eben auch mit einem PIC32 (32-Bit-Core) laufen lassen möchte. Und da bei 16 und 32 Bit Prozessoren im Regelfall Datenwörter auf auf durch 2 (bzw. 4) teilbaren Adressen liegen müssen würde ein Struct bestehend aus einem Byte und einem 32-Wort im Speicher als 1 Byte + 3 Füllbytes + 4 Byte für ein 32-Bit-Wort auftauchen. In meiner Nachrichtenwarteschalnge aber als 1 Byte + 4 Byte dargestellt, welche mit hoher Wahrscheinlichkeit nicht auf einer durch 4 Teilbaren Adresse landen und zu einer Ausnahmebehandlung/einem Reset führen werden. Also funktionniert mein ursprüglicher Ansatz (vom ATmega), einem Pointer auf die Struct die Adresse der aktuellen Nachricht zuzuweisen, nicht mehr. So daß ich für PIC24/33/32 in jedem Fall von Hand die Struct füllen muß. Da ich den Code aber sowohl auf dem AVR als auch auf dem PIC32 ohne Änderungen laufen lassen möchte, stellte sich für mich die Frage, wie ich den nun am besten die einzelnen Bytes in meine Struct einsortiere, ohne daß der Compiler für einen der Prozessoren unnötig schlechten Code erzeugt. Heißt es eigentlich der/die oder das Struct? ;-)
Bernd M. wrote: > Wer sagt das Structs böse sind. Sie machen Dein Programm einfach > übersichtlicher. Klassen in C++ sind natürlich noch etwas komfortabler. Im Bezug auf Serialisierung... > Da ich den Code aber sowohl auf dem AVR als auch auf dem PIC32 ohne > Änderungen laufen lassen möchte, stellte sich für mich die Frage, wie > ich den nun am besten die einzelnen Bytes in meine Struct einsortiere, > ohne daß der Compiler für einen der Prozessoren unnötig schlechten Code > erzeugt. Genau das mein ich ja. Gibts ne Bib die anhand der Architektur den besten Code auswählt und dann serialisiert? > Heißt es eigentlich der/die oder das Struct? ;-) Im Deutschen "die Struktur" aber ich bin für "das Struct".
Bernd M. wrote: > Das Hauptproblem, weswegen ich diesen Thread überhaupt gepostet habe, > war, daß ich das ganze eben auch mit einem PIC32 (32-Bit-Core) laufen > lassen möchte. Und da bei 16 und 32 Bit Prozessoren im Regelfall > Datenwörter auf auf durch 2 (bzw. 4) teilbaren Adressen liegen müssen > würde ein Struct bestehend aus einem Byte und einem 32-Wort im Speicher > als 1 Byte + 3 Füllbytes + 4 Byte für ein 32-Bit-Wort auftauchen. Naja, nicht wirklich. Zum Teil hast du Recht ... Auf z.B. ARM7 ist es das Gleiche mit dem Data-Alignment, trotzdem kann man mit packed structs arbeiten. Der Compiler macht das dann schon richtig ... Dabei kommt dann allerdings sowas raus: Beitrag "[WinARM] Data-Alignment lässt grüßen" MfG Thomas Pototschnig
> Dabei kommt dann allerdings sowas raus: Ist doch schon. Wenn irgenwann mal .NET und C++/CLI auf Mikrocontrollern läuft (s. http://caxapa.ru/thumbs/126953/Masters2008.pdf Seite 63), wird bestimmt alles viel einfacher;-) Was sind denn packed structs?
Bernd M. wrote: >> Dabei kommt dann allerdings sowas raus: > Ist doch schon. > Wenn irgenwann mal .NET und C++/CLI auf Mikrocontrollern läuft (s. > http://caxapa.ru/thumbs/126953/Masters2008.pdf Seite 63), wird bestimmt > alles viel einfacher;-) > > Was sind denn packed structs? Wenn man ein struct als packed deklariert, dann gibt es kein padding. D.h. 1Byte hat genau 1Byte im struct und keine Füllbytes. Ist notwendig für Header irgendwelcher Files, die sich nicht um 32bit-Alignment kümmern. Der Compiler fieselt das dann mit mehreren 32Bit Zugriffen, die 32bit-aligned sind, und logischen Operationen zu dem Wert zusammen, den man dann auch wirklich haben möchte. Ist ziemlich brutal und sieht wild aus, aber es funktioniert **g** Beim GCC gehts so:
1 | struct test_t { |
2 | int a; |
3 | char b; |
4 | int c; |
5 | } __attribute__((__packed__)); |
Quelle: http://sig9.com/articles/gcc-packed-structures MfG Thomas Pototschnig
Packed Structs sind aber nicht wirklich Standard. Und das Problem mit Little-Endian und Big-Endian lösen sie auch nicht, oder? Also nur im Einzelfall zu gebrauchen, wenn man genau weiß mit was der Compiler daraus genau macht.
Bernd M. wrote: > Packed Structs sind aber nicht wirklich Standard. Und das Problem mit > Little-Endian und Big-Endian lösen sie auch nicht, oder? Also nur im > Einzelfall zu gebrauchen, wenn man genau weiß mit was der Compiler > daraus genau macht. Genau so ist es.
Thomas Pototschnig wrote: > Beim GCC gehts so: >
1 | > struct test_t { |
2 | > int a; |
3 | > char b; |
4 | > int c; |
5 | > } __attribute__((__packed__)); |
6 | >
|
Wenn man Problemen aus dem Weg gehen will, dann achtet man auf das Alignment der Komponenten, d.h. man legt nicht einen int (was soll das sein? short? long?) an Offset 3 resp. 5:
1 | typedef struct |
2 | {
|
3 | long a; |
4 | char b; |
5 | int :8; // dummy |
6 | int :8; // dummy |
7 | int :8; // dummy |
8 | long c; |
9 | } data_t; |
d.h. wenn für die Größe S einer Komponenten gilt S=2**a * u mit u ungerade, dann alignt man auf 2 ** min (a, A) mit A=2 oder 3. Und die Endianness kann man einfach zu Laufzeit abtesten, wobei ein guter Compiler das zur Compilezeit schafft:
1 | typedef union |
2 | {
|
3 | unsigned char asByte[4]; |
4 | unsigned long asLong; |
5 | } data32_t; |
6 | |
7 | ...
|
8 | data32_t data = { .asLong = 0x12345678 }; |
9 | if (data.asByte[0] == 0x12) |
10 | etc... |
Wenn der Compiler gut ist, dann erkennt er, daß der Code, der bei nicht passender Endianness durchlaufen würde, tot ist und kloppt ihn in die Tonne.
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.