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?
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).
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:
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.
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
typedefstruct
2
{
3
longa;
4
charb;
5
int:8;// dummy
6
int:8;// dummy
7
int:8;// dummy
8
longc;
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
typedefunion
2
{
3
unsignedcharasByte[4];
4
unsignedlongasLong;
5
}data32_t;
6
7
...
8
data32_tdata={.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.