Also ich weiß ja nicht ob's schon ein alter Hut ist, aber ich habe heute
feststellen müssen das mit dem GCC Compiler folgender Effekt bei
Strukturen bzw Unionen auftritt: Wenn Man eine Struktur A erstellt in
dem am Anfang als Member eine andere Struktur B und als folge-Member ein
Datenarray C steht und man diese Struktur A dann als eine Union schreibt
die als erstem Member Struktur A enthält und als zweites Member
Datenarray D mit der Länge von Struktur A dann sind zwischen Struktur B
und Datenarray C immer 3bytes eingeschoben. Hier der Beispielcode aus
Geany:
1
#include<stdio.h>
2
3
typedefstruct{
4
unsignedhead1:4;
5
unsignedhead2:4;
6
}_struct_B;
7
8
typedefstruct{
9
_struct_Bstruct_B;
10
chararrayC[5];
11
}_struct_A;
12
13
typedefunion{
14
_struct_Astruct_A;
15
unsignedchararrayD[6];
16
}_array;
17
18
intmain()
19
{
20
_arrayarray;
21
array.struct_A.arrayC[0]=0xFF;//Anfangsbyte vom Array kennzeichnen
22
printf("%i",array.arrayD[4]);
23
return0;
24
}
In der main-routine ist erst arrayD[4]=255 und nicht arrayD[1]. Wenn man
allerdings in _struct_A das erste Member ein Char-Array ist, dann tritt
dieser Effekt nicht auf, folglich werden mit arrayD[n] mit n als
Laufnummer sauber durchweg alle bytes der struktur angesprochen ohne die
3 byte extra dazwischen.
Ist euch dieser Effekt bekannt?
Das kommt durch dein Bitfield. Dein struct ist ein unsigned int
(vermutlich 4 Bytes) groß, obwohl du nur 8 Bits benutzt. Gibt aber eine
GCC Erweiterung wo man auch unsigned char fürs Bitfield nehmen kann,
dann müsste es so sein wie du erwartest. Im übrigen ist IMHO diese
Verwendung von union undefiniertes Verhalten.
Bitfields sind nicht portabel.
So kann es sein, dass Bitfelder entsprechend dem angegeben Basistyp
aligned werden. Also
unsigned x : 4;
entsprechend den Regeln von "unsigned", und
unsigned char x : 4;
entsprechend den Regeln von "unsigned char".
Sebastian V. schrieb:> Das kommt durch dein Bitfield. Dein struct ist ein unsigned int> (vermutlich 4 Bytes) groß, obwohl du nur 8 Bits benutzt.
Du willst damit sagen das er sowieso int große Felder (in dem Fall 32
also 4byte) benutzt um die struktur abzulegen?
Wie kann es aber sein wenn ich nur eine Struktur deklariere mit
dieversen Bitfields, und die in die Union schicke die dann aber sauber
hintereinander auslesen kann?
be s. schrieb:> Prüfe mal mit sizeof, wie gross deine Datenstrukturen sind.
folgende Werte sind entstanden:
sizeof(_struct_A)=12
sizeof(_struct_B)=4
sizeof(_array)=12
da sind wieder die 3 zusätzlichen bytes
Ludwig S. schrieb:> folgende Werte sind entstanden:> sizeof(_struct_A)=12> sizeof(_struct_B)=4> sizeof(_array)=12
Habe ich bei mir auch. Ich vermute, dass der Compiler Padding-Bytes
einfügt. Ändere ich struct_A auf
Ludwig S. schrieb:> Ah Ok ich habs gerafft... für jede neue Zuweisung wird immer die> bytebreite des systems verbraucht. Ist das richtig?
Fast. Es wird die Wordbreite benutzt (ein Byte ist (fast) immer 8 bits).
Stichwort Alignment. Wie aber schon erwähnt, kannst du mit
1
__attribute__((packed))
den Compiler dazu bringen kein Platz zu verschwenden.
Eric B. schrieb:> Wie aber schon erwähnt, kannst du mit __attribute__((packed))> den Compiler dazu bringen kein Platz zu verschwenden.
Allerdings ist es – je nach Architektur – danach nicht mehr
sichergestellt, ob man auf die einzelnen Elemente überhaupt noch
zugreifen kann oder eine unaligned exception bekommt.
Als dritte Variante gibt es auch noch den Fall, dass der Zugriff zwar
möglich ist, man aber damit Performanceeinbußen in Kauf nehmen muss,
weil statt nur eines Buszyklusses zwei ausgeführt werden müssen, um
die Daten zu erhalten.
Ist ja nicht so, dass das padding da aus Spaß durch den Compiler
eingefügt wird.
Sinnvollerweise legt man daher bei struct die größten Elemente als
erstes an und geht von da zu den kleineren. Damit hat man das
padding dann bestenfalls noch am Ende der gesamten struct.
Hm... Schade eigentlich... Weil will gerade n Protokoll implementieren
und da wäre das natürlich wichtig das die Reihenfolge stimmt. Dann
könnte man nämlich die Sache ziemlich einfach per union auslesen. So ist
das alles zwar nicht sonderlich schlimm aber leider nicht so schön. Wie
verhält sich denn das mit dem __attribute__(packed) das gilt denn
wahrscheinlich für das ganze programm oder nur für die jeweilige
Quelltextdatei?
Ludwig S. schrieb:> Wie> verhält sich denn das mit dem __attribute__(packed) das gilt denn> wahrscheinlich für das ganze programm oder nur für die jeweilige> Quelltextdatei?
Nur für das struct (oder sogar nur für einzelne Variablen im struct) wo
du es hinschreibst.
Ludwig S. schrieb:> Weil will gerade n Protokoll implementieren und da wäre das natürlich> wichtig das die Reihenfolge stimmt.
Denk aber dran, dass bei Bitfields fast alles “implementation-defined”
ist, dein Quellcode ist damit also nicht mehr portabel, sondern seine
Funktion nur auf bestimmten Architekturen mit einem konkreten Compiler
garantiert.
Ludwig S. schrieb:> Dann> könnte man nämlich die Sache ziemlich einfach per union auslesen. So ist> das alles zwar nicht sonderlich schlimm aber leider nicht so schön.
Dieses Vorgehen ist so oder so nicht schön, denn eine solche Verwendung
einer Union erzeugt undefiniertes Verhalten, dies wurde hier bereits
angesprochen. Warum nicht für die zu konvertierenden Strukturen eine
Funktion spendieren, die die Daten entsprechend in ein Array umwandelt
(Stichwort: De/Serialisierung)?
Jörg W. schrieb:> Allerdings ist es – je nach Architektur – danach nicht mehr> sichergestellt, ob man auf die einzelnen Elemente überhaupt noch> zugreifen kann oder eine unaligned exception bekommt.
Sollte der Compiler für solche Architekturen dann nicht Instructions
nehmen die auch ohne korrektes Alignment funktionieren? Und wenn er
jedes Byte einzelnd lesen und wieder zu einem int zusammenbauen muss.
Sebastian V. schrieb:> Sollte der Compiler für solche Architekturen dann nicht Instructions> nehmen die auch ohne korrektes Alignment funktionieren?
Das müsste der GCC dokumentieren, denn es ist seine private Erweiterung.
Kommt hinzu, dass die entsprechende Implemetierung gar nicht notwendig
eine Sache des Compilers ist: es gab/gibt Varianten, bei denen das
OS den unaligned trap abgefangen hat und dann byteweisen Zugriff in
Software emuliert. In so einem Falle wäre es wenig zweckmäßig, wenn
der Compiler sein eigenes Süppchen kochen würde.
Ohne das Attribut ist garantiert, dass alles funktioniert.
Bei den meisten heute verwendeten Architekturen dürfte ohnehin die
dritte Variante der Fall sein, d. h. man büßt damit vor allem
Performance ein.
Wenn man schlau ist, baut man gleich das Protokoll sinnvoll so auf,
dass kein Padding nötig wird (bzw. integriert das Padding selbst in
die Daten).
Jörg W. schrieb:> Wenn man schlau ist, baut man gleich das Protokoll sinnvoll so auf,> dass kein Padding nötig wird (bzw. integriert das Padding selbst in> die Daten).
Leider sieht man das eigentlich nirgendwo. Ich stand vor kurzer Zeit
auch vor dem Problem ein fremdes Protokoll einzulesen und habe mich
gegen die packed struct Variante entschieden und es (hoffentlich)
portabel programmiert. Jetzt habe ich für jedes struct eine unpack/pack
Funktion welche aus einem Array von Bytes ein struct füllt oder
umgekehrt. Neben unterschiedlich großen ints gibts auch noch Strings mit
dynamischer Länge, weshalb die packed struct Variante sowieso schwierig
wäre. Für die verschieden Großen ints gibts dann nochmal separate
Funktionen welche diese einlesen/schreiben. Anfangs habe ich die
Umwandlung Byte Array zu Int noch mit Bit Shifts gemacht, aber nachdem
ich mir den generierten Code angeschaut habe nutze ich jetzt an wenigen
zentralen Stellen memcpy (Protokoll und Zielplattformen nutzen alle
Little-Endian). Trotzdem bin ich mit dieser Lösung nur teilweise
zufrieden. Die unpack/pack Funktionen generieren relativ viel Code mit
fast nur load und store Instructions. Außerdem scheint es irgendwie
verschwenderisch ~100 Bytes zu kopieren nur um etwas Padding einzufügen.
Welche Methoden nutzt ihr um solche binären Protokolle zu entpacken oder
zu schreiben?