Forum: Compiler & IDEs Interessante Entdeckung im GCC Compiler


von Ludwig S. (big_l)


Lesenswert?

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
typedef struct{
4
        unsigned head1 : 4;
5
        unsigned head2 : 4;
6
        } _struct_B;
7
8
typedef struct{
9
              _struct_B struct_B;
10
              char arrayC[5];
11
        } _struct_A;
12
        
13
typedef union{
14
       _struct_A struct_A;
15
       unsigned char arrayD[6];
16
       } _array;
17
18
int main()
19
{
20
_array array;
21
array.struct_A.arrayC[0]=0xFF; //Anfangsbyte vom Array kennzeichnen
22
printf("%i",array.arrayD[4]);
23
return 0;
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?

von Sebastian V. (sebi_s)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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

von B. S. (bestucki)


Lesenswert?

Prüfe mal mit sizeof, wie gross deine Datenstrukturen sind. Auf einem PC 
mit sizeof(int) == 4 tippe ich auf folgende Werte:
1
sizeof(_struct_B) == 4
2
sizeof(_struct_A) == 9
3
sizeof(_array) == 9

Nochwas: Identifier, die mit einem Underscore beginnen, sind für den 
Compiler reserviert und dürfen nicht verwendet werden.

von Little B. (lil-b)


Lesenswert?

Ja, für stuct_b werden im struct_a 4 Bytes reserviert.
Es sei denn, du verwendest __attribute__((packed))

von Ludwig S. (big_l)


Lesenswert?

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

von Ludwig S. (big_l)


Lesenswert?

Ah Ok ich habs gerafft... für jede neue Zuweisung wird immer die 
bytebreite des systems verbraucht. Ist das richtig?

von B. S. (bestucki)


Lesenswert?

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
1
typedef struct{
2
  _struct_B struct_B;
3
  char arrayC[8];
4
} _struct_A;
erhalte ich das gleiche Ergebnis.

von Eric B. (beric)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von Ludwig S. (big_l)


Lesenswert?

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?

von Sven B. (scummos)


Lesenswert?

Das _attribute_ ist normalerweise für einzelne structs ...

von Sebastian V. (sebi_s)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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

von Sebastian V. (sebi_s)


Lesenswert?

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.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

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

von Sebastian V. (sebi_s)


Lesenswert?

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?

von (prx) A. K. (prx)


Lesenswert?

Wenn ein Protokoll maschinenübergreifen ist, dann hast du sowieso die 
möglicherweise verschiedene Bytereihenfolge am Hals.

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.