structs auf 8-Bit-MCUs

#1055985
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?
Gast #1055990
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.
#1055995
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.
#1055999
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?
#1056009
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).
Persönliche Seite #1056190
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.
#1056252
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.
#1056258
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 ...
Gast #1056285
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).
#1056306
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? ;-)
Persönliche Seite #1056315
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".
#1056317
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
#1056356
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
Persönliche Seite #1056374
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren