Forum: Compiler & IDEs structs auf 8-Bit-MCUs


von Be M. (bemi)


Lesenswert?

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?

von holger (Gast)


Lesenswert?

>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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Be M. (bemi)


Lesenswert?

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?

von Be M. (bemi)


Lesenswert?

Letztlich geht es mir darum structs zu serialisieren und dann auf einem 
anderen Mikrocontroller diesen Datenstrom wieder in ein Struct zu 
verwandeln.

von (prx) A. K. (prx)


Lesenswert?

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).

von Be M. (bemi)


Lesenswert?

Programmieren kann man alles. Irgendwie;-)

Fragt sich am Ende nur, wie effizient der Code ist.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Be M. (bemi)


Lesenswert?

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.

von Thomas P. (pototschnig)


Lesenswert?

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 ...

von (prx) A. K. (prx)


Lesenswert?

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.

von Be M. (bemi)


Lesenswert?

Ok, ich denke, ich weiß, wie ich es machen werde.

Danke,
Bernd

von Jürgen (Gast)


Lesenswert?

> 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).

von Manuel S. (thymythos) Benutzerseite


Lesenswert?

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.

von Be M. (bemi)


Lesenswert?

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? ;-)

von Manuel S. (thymythos) Benutzerseite


Lesenswert?

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".

von Thomas P. (pototschnig)


Lesenswert?

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

von Be M. (bemi)


Lesenswert?

> 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?

von Thomas P. (pototschnig)


Lesenswert?

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

von Be M. (bemi)


Lesenswert?

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.

von Simon K. (simon) Benutzerseite


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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
Noch kein Account? Hier anmelden.