Forum: Compiler & IDEs Kann ich garantieren, dass ein C Struct genau 3 Byte im Speicher belegt?


von Sebastian B. (mircobolle)


Lesenswert?

Hallo,

wenn ich einen Array mit dem struct definiere, welcher 3 Byte 
beinhaltet. Ist dann garantiert, dass N * 3 Byte für das Array im RAM 
belegt werden?

Beispiel für den struct
1
struct beispiel_struct{
2
     unsigned char byte_1;    /* 1 Byte */
3
     unsigned int byte_2_3;   /* 2 Byte */
4
};

Gruss

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


Lesenswert?

Nein, es könnten auch N * 4 Bytes sein (Maschine mit 16-bit int und
Speicherausrichtung, eher selten) oder N * 8 Bytes sein (Maschine
mit 32-bit int und Speicherausrichtung, sehr häufig anzutreffen).

Auch muss ein Byte nicht zwingend 8 Bits haben, allerdings sind
Maschinen, auf denen das nicht der Fall ist, eher seltene Vögel
(irgendwelche DSPs, die als kleinste Einheit 32-bit-Zahlen
adressieren können).

von Sebastian B. (mircobolle)


Lesenswert?

Wäre es eine Lösung alles als "Byte-Stream" in den RAM zu schreiben und 
dann per Lese-Routine, die Daten in char, int etc. Variablen zu laden, 
wie sie gerade benötigt werden ?

quasi:
1
unsigned char byte_array[3];
2
3
unsigned char byte_1 = byte_array[0];
4
unsigned int byte_2_3 = (byte_array[1] << 8)|(byte_array[2]);

ggf. das ganze dann bei Bedarf in Struktur laden.

Das würde mir dazu spontan einfallen.
Also char Array, und dann bei Bedarf in lokale Struktur laden.

Oder seh ich da was falsch?

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


Lesenswert?

Ja, ist eine Lösung, die auch portabel ist.

von Gast (Gast)


Lesenswert?

Nö, ist nicht portabel.

Wer sagt denn das byte_array[1] immer das High-Byte und
byte_array[2] immer das Low Byte ist.

von Detlev T. (detlevt)


Lesenswert?

@Sebastian B.
In ANSI-C sind für die verschiedenen Typen nur Mindestbereiche 
angegeben, die man damit darstellen können muss. Wieviele Bits/Bytes sie 
im einzelnen belegen, garantiert einem da keiner. Ich sehe da nur die 
Möglichkeit, an einer Stelle entsprechende Typen zu erstellen (so was 
wie uint8_t, uint16_t), und Anweisungen an den jeweiligen Compiler, 
diese Daten entsprechend gepackt werden sollen. Um manuelle Eingriffe 
beim Portieren auf andere Systeme/Compiler kommt man wohl nicht ganz 
herum.

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


Lesenswert?

Gast wrote:

> Wer sagt denn das byte_array[1] immer das High-Byte und
> byte_array[2] immer das Low Byte ist.

Die Definition, was wo in byte_array[] steht.

Normalerweise macht man so einen Zirkus ja nur, wenn das Array über
bspw. ein externes Protokoll gefüllt wird, dessen Daten man intern
weiter benutzen möchte (Netzwerkprotokoll, SCSI-Protokoll, als zwei
Beispiele, bei denen ich das so schon gesehen habe).

von Sebastian B. (mircobolle)


Lesenswert?

Jörg Wunsch wrote:
> Normalerweise macht man so einen Zirkus ja nur, wenn das Array über
> bspw. ein externes Protokoll gefüllt wird, dessen Daten man intern
> weiter benutzen möchte (Netzwerkprotokoll, SCSI-Protokoll, als zwei
> Beispiele, bei denen ich das so schon gesehen habe).

Bei mir dreht sicher der Zirkus um eine "Befehls-Sequenz", die ich aus 
dem EEPROM lade. Diese Befehlssequent besteht jeweils aus einer 1 Byte 
ID und einem 2 Byte Wert. Deshalb auch die 3 Byte ;-)

Mir ging es nur darum, dass ich nicht zu viel Platz im RAM 
"verschwenden" möchte, in dem ich die Sequenz schön formatiert in ein 
Array mit dem Typ struct lade.

Ich gehe nun so vor, dass ich den EEPROM Inhalt ins RAM "spiegle".. und 
dann bei Bedarf die Daten mit einem u8* Zeiger in meine "lokale 
Struktur" lade um damit an dieser Stelle damit weiter zumachen.

Hintergrund:
---------------
Die Abarbeitung, also auch das Laden der Sequenzschritte direkt aus dem 
EEPROM funktioniert, aber wird natürlich durhc die langsame 
Zugriffsgeschwindigkeit ausgebremst. Ich wollte die Abarbeitung jetzt 
beschleunigen in dem ich den EEPROM Inhalt zu Beginn komplett in den RAM 
lade.

Gruss

von Christian R. (supachris)


Lesenswert?

Dafür gibts doch #pragma pack(), inwieweit das von allen Kompilern und 
Zielplattformen unterstützt wird, weiß ich aber auch nicht.

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


Lesenswert?

Christian R. wrote:

> Dafür gibts doch #pragma pack(), inwieweit das von allen Kompilern und
> Zielplattformen unterstützt wird, weiß ich aber auch nicht.

Da wir hier im GCC-Forum sind: gar nicht.  GCC nimmt keine Pragmas,
weil Pragmas ein miserabel durchdachtes Konzept sind.  (Sie stehen
zu weit weg von den Dingen, die man deklariert, damit ist man nicht
sehr ,,zielgenau''.)

GCC nimmt _attribute_, und so sollte es nicht wundern, dass man
das mit __attribute__((pack)) macht.

von Manuel Stahl (Gast)


Lesenswert?

Zumindest der avr-gcc versteht als Option -fpack-structs

von Matthias L. (Gast)


Lesenswert?

>Speicherausrichtung

Ich würde das dann so anlegen, falls es mit dem Speicher nicht zu knapp 
ist:
1
struct beispiel_struct{
2
     unsigned char dummy;     /* für 4er Grenze */
3
     unsigned char byte_1;    /* 1 Byte */
4
     unsigned int byte_2_3;   /* 2 Byte */
5
};

von Christian R. (supachris)


Lesenswert?

Ok, ok, dann halt per Attribut :)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Manuel Stahl wrote:
> Zumindest der avr-gcc versteht als Option -fpack-structs

Das bringt leider nichts, weil dann alle Structs/Unions gepackt werden 
und nicht nur die gewünschte. Ausserdem müssten dann alle Module und 
Bibliotheken neu übersetzt werden, um Interlinkfähigkeit zu garantieren.

von muhkuh (Gast)


Lesenswert?

was ist hier mit 4er grenze gemeint?

struct beispiel_struct{
     unsigned char dummy;     /* für 4er Grenze */
     unsigned char byte_1;    /* 1 Byte */
     unsigned int byte_2_3;   /* 2 Byte */
};


und wieso können das N*8 byte sein (jetzt ohne den dummy) bei ner 32 bit 
maschine sind doch N*4 byte oder??

mfg. muhkuh

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


Lesenswert?

muhkuh wrote:

> und wieso können das N*8 byte sein (jetzt ohne den dummy) bei ner 32 bit
> maschine sind doch N*4 byte oder??

Weil der enthaltene 32-bit-int am Schluss steht und normalerweise
auf einer 32-bit-Grenze ausgerichtet wird.  Manche CPUs erzeugen
einen Trap, wenn sie `unaligned'-Zugriffe bekommen, andere CPUs
(wie IA32) arbeiten weniger effektiv, da sie sich die Zahl über
Byte-Extraktion aus dem Ergebnis zweier Buszyklen zusammennageln
müssen.  Die Zeiten, da die Busse nur 8 bit breit waren, sind ja
schon ein Weilchen vorbei.

Daher ist es eine gute Idee, structs "vom Großen zum Kleinen"
aufzufüllen, d. h. zuerst alle 64-bit-Daten (so es welche gibt),
dann alle mit 32 bits, usw.  Auf diese Weise wäre ggf. höchstens
am Ende ein Auffüllen mit ungenutzten Bytes notwendig (padding).
Wenn man sie ,,wild'' mischt, dann würde das Padding auch zwischen
den einzelnen Teilen erfolgen und daher mehr Platz verschwendet.

von muhkuh (Gast)


Lesenswert?

Wären dann aber nicht im schlimmsten Fall 3 Füllbytes zwischen dem Char 
und dem Int? das würde dann insgesamt 8 Byte machen aber wie kommt man 
dann auf N*8 also 16 Byte?

Sollte doch wie folgt im Speicher liegen:

char Byte, freies Byte, freies Byte, freies Byte, int (4 Byte) und dann 
aus also in Summe 8 Byte.

Auf 32 Bit Maschinen ist das Alignment ja 4, nicht 8 oder?

Verstehe folgendes nicht ganz:
Nein, es könnten auch N * 4 Bytes sein (Maschine mit 16-bit int und
Speicherausrichtung, eher selten) oder N * 8 Bytes sein (Maschine
mit 32-bit int und Speicherausrichtung, sehr häufig anzutreffen).

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


Lesenswert?

muhkuh wrote:

> Wären dann aber nicht im schlimmsten Fall 3 Füllbytes zwischen dem Char
> und dem Int? das würde dann insgesamt 8 Byte machen aber wie kommt man
> dann auf N*8 also 16 Byte?

Die letzte Schlussfolgerung (die N = 2 impliziert) ist einfach
falsch.

N war die Größe des Arrays.

> Verstehe folgendes nicht ganz:
> Nein, es könnten auch N * 4 Bytes sein (Maschine mit 16-bit int und
> Speicherausrichtung, eher selten) oder N * 8 Bytes sein (Maschine
> mit 32-bit int und Speicherausrichtung, sehr häufig anzutreffen).

Was genau verstehst du daran nicht?

von muhkuh (Gast)


Lesenswert?

Wieso es bei 16 Bit systemen N * 4 <- versteh die 4 Byte nicht und bei 
32 Bit Systemen N * 8 Byte sind.

Dachte es wären N*2 bei 16 Bit und N*4 bei 32 Bit Systemen.

von uint32_t (Gast)


Lesenswert?

Beispiel für den struct

struct beispiel_struct{
     uint8_t;    /* 1 Byte */
     uint16_t;   /* 2 Byte */
};

Gruss

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


Lesenswert?

muhkuh wrote:
> Wieso es bei 16 Bit systemen N * 4 <- versteh die 4 Byte nicht und bei
> 32 Bit Systemen N * 8 Byte sind.
>
> Dachte es wären N*2 bei 16 Bit und N*4 bei 32 Bit Systemen.

Du hast immer noch nicht kapiert, dass "N" beim OP die Anzahl der
Elemente seines Arrays sein sollte, glaub ich.  Folglich belegt das
Array N*<irgendwas> an Speicher.

Die 4 gibt es nur bei 16-bit-Systemen, die ein memory alignment
benötigen, weil dann der uint16_t auf einer 16-bit-Grenze ausgerichtet
werden muss.  Dafür muss nach dem uint8_t-Element ein padding byte
eingefügt werden.  Solche Systeme sind aber heutzutage eher selten
anzutreffen (16-bit-Systeme wohl insgesamt), eine PDP-11 fiele mir
auf Anhieb noch ein.  Andere 16-bit-Systeme wie 8086 haben kein
Alignment benötigt (und hätten von einem solchen, im Gegensatz zu den
Nachfolgern ab 80386 auch nicht profitiert).

Bei 8-bit-Systemen wiederum braucht man kein Alignment, da ja der
Bus sowieso immer in Einheiten von 8 Bit zugegriffen wird.

von Michael G. (linuxgeek) Benutzerseite


Lesenswert?

Sebastian B. wrote:
> Hallo,
>
> wenn ich einen Array mit dem struct definiere, welcher 3 Byte
> beinhaltet. Ist dann garantiert, dass N * 3 Byte für das Array im RAM
> belegt werden?

Von sowas auszugehen ist ein Design-Fehler, das haengt von der 
Implementierung im Compiler ab, die nicht nur von Compiler zu Compiler 
sondern auch von Version zu Version verschieden sein kann.

von Thomas (Gast)


Lesenswert?

Jörg Wunsch schrub:

> Da wir hier im GCC-Forum sind: gar nicht.  GCC nimmt keine Pragmas,
> weil Pragmas ein miserabel durchdachtes Konzept sind.  (Sie stehen
> zu weit weg von den Dingen, die man deklariert, damit ist man nicht
> sehr ,,zielgenau''.)

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

Ist das beim AVR-GCC etwa anders?

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


Lesenswert?

War mir komplett neu.  Möglicherweise funktioniert das damit auch
beim AVR-GCC.

von let (Gast)


Lesenswert?

Diese pragmas funktionieren beim GCC nicht in jedem Fall.
Der MinGW kann damit umgehen, wobei das push/pop anscheinend
nicht geht.
Der arm-gcc ignoriert dieses pragma kommentarlos.

Jeder ernstzunehmende C-Compiler kann Strukturen packen. Nur
leider ist die Syntax für die Deklaration nicht einheitlich.

von yalu (Gast)


Lesenswert?

> War mir komplett neu.

Mir auch.

> Möglicherweise funktioniert das damit auch beim AVR-GCC.

Nein:

  warning: ignoring #pragma pack

Aber der AVR-GCC braucht so etwas ja auch nicht, weil er Strukturen
sowieso ins Byte-Raster packt (also wie pack(1)).

von krase (Gast)


Lesenswert?

Den gcc und damit auch avr-gcc kann man so anweisen, die Elemente einer 
Struktur zu packen:
1
struct foo {
2
    char    a;
3
    short   b;
4
    int     c;
5
} __attribute__ ((packed));

Quellen:
http://www.linuxjournal.com/article/5783
http://www.unixwiz.net/techtips/gnu-c-attributes.html

von Christian R. (supachris)


Lesenswert?

yalu wrote:

> Aber der AVR-GCC braucht so etwas ja auch nicht, weil er Strukturen
> sowieso ins Byte-Raster packt (also wie pack(1)).

Immer, oder nur mit dem packed Attribut? Wenn der das immer macht, wäre 
es ja auch ziemlich gefahrlich, da man dann zwische meinetwegen einem 
AVR und einem Windows Programm nicht einfach eine Struktur 1:1 
übertragen könnte, wenn man nicht den PC-Kompiler wiederum anweist, auf 
1-Byte zu packen....

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


Lesenswert?

Christian R. wrote:

> Immer, oder nur mit dem packed Attribut? Wenn der das immer macht, wäre
> es ja auch ziemlich gefahrlich, da man dann zwische meinetwegen einem
> AVR und einem Windows Programm nicht einfach eine Struktur 1:1
> übertragen könnte, wenn man nicht den PC-Kompiler wiederum anweist, auf
> 1-Byte zu packen....

Das ist nicht gefährlich, sondern C.  structs sind ja nicht in erster
Linie dafür erfunden worden, dass man sie bestmöglich zwischen einem
Abakus und einer Cray hin und her transferieren kann, sondern das
Programm auf dem Computer soll damit optimal arbeiten können.

Extern ist alles ein "octet stream", und um Dinge wie padding, byte
order etc. musst du dir beim Datenaustausch Gedanken machen.  Dafür
gibt's im OSI-Modell eine eigene Schicht (presentation layer).

von Christian R. (supachris)


Lesenswert?

Ich meinte eher: Gefährlich, wenn man es nicht weiß, und Daten über 
Strukturen austauschen will.

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


Lesenswert?

Christian R. wrote:
> Ich meinte eher: Gefährlich, wenn man es nicht weiß, und Daten über
> Strukturen austauschen will.

Dann hat man die falsche Aufgabe, oder die fehlenden Vorkenntnisse
für die Aufgabe.

Es gibt halt auch Maschinen, bei denen man einfach mit
__attribute__((packed)) nicht einmal mehr lauffähigen Code bekäme
(praktisch alle 32- und 64-bit-RISC-Systeme fallen hier hinein),
andererseits wirst du nicht automatisch 64-bit-padding-Regeln auf
einem AVR anwenden wollen, nur damit die Speicherstrukturen
binärkompatibel zu einer UltraSPARC sind.

Wer also blindlings Speicherstrukturen 1:1 zwischen völlig verschiedenen
Architekturen austauscht, hat irgendwo einen prinzipiellen Fehler
gemacht.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Jörg Wunsch wrote:
> Es gibt halt auch Maschinen, bei denen man einfach mit
> __attribute__((packed)) nicht einmal mehr lauffähigen Code bekäme
> (praktisch alle 32- und 64-bit-RISC-Systeme fallen hier hinein),

Zum Beispiel?

Das Attribut sagt dem Compiler doch gerade, dass er aufpassen muss, da 
dies Daten möglicherweise(!) unaligned im Speicher liegen. Es wird daher 
Code erzeugt, der auf die Daten schlimmstenfalls byte-weise zugreift und 
sie im Prozessor wieder zusammenbaut.

Einige RISC Architekturen (ARM seit v6) erlauben durchaus "echte" 
unaligned Zugriffe.

Gruß
Marcus

PS: Irgendwie habe ich das Gefühl, das habe ich letztens in einem 
anderen Thread auch schon geschrieben.

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


Lesenswert?

Marcus Harnisch wrote:

>> Es gibt halt auch Maschinen, bei denen man einfach mit
>> __attribute__((packed)) nicht einmal mehr lauffähigen Code bekäme
>> (praktisch alle 32- und 64-bit-RISC-Systeme fallen hier hinein),

> Zum Beispiel?

sparc64 (aka. UltraSPARC)

> Das Attribut sagt dem Compiler doch gerade, dass er aufpassen muss, da
> dies Daten möglicherweise(!) unaligned im Speicher liegen. Es wird daher
> Code erzeugt, der auf die Daten schlimmstenfalls byte-weise zugreift und
> sie im Prozessor wieder zusammenbaut.

Nö.  Der packt die Strukturen einfach, wenn du dann drauf zugreifen
willst, knallt es (unaligned trap).  Du hast ihn ja mit dem
__attribute__((packed)) explizit daran gehindert, die Struktur
so aufzubauen, dass man sie auch sinnvoll benutzen kann.

> Einige RISC Architekturen (ARM seit v6) erlauben durchaus "echte"
> unaligned Zugriffe.

Ja, aber die sind dann teurer, weil sie mehrere Buszyklen brauchen.
So ist es ja letztlich auch bei IA32.

von Christian R. (supachris)


Lesenswert?

Sicher muss man wissen, ob die entsprechenden an der Übertragung 
beteiligten Architekturen das unterstützen. Ich schreib ja auch keinen 
Code, der ohne Änderung auf einem Win32 PC und einer Sparc 64 irgendwas 
laufen soll.

von let (Gast)


Lesenswert?

Beim ARM7 (ARMv4) kann man unter Verwendung des arm-gcc oder RealView
auf gepackte Strukturen zugreifen. Die Compiler erzeugen entsprechenden
Code um die Daten passend zurecht zu schieben. Ohne dieses Feature
wären die Compiler für RISC Prozessoren wohl auch kaum zu gebrauchen.
Der TCP-Stack lwip würde dann auch auf keiner RISC Architektur
laufen, da der heftig Gebrauch von gepackten Strukturen macht.

Anders sieht es beim Casten aus. Nach einer Datenübertragung liegen die
Strukturen ja zunächst als Byte Array vor.
1
void processData(uint8_t* au8Data)
2
{
3
   struct SomeStruct* ptr = (struct SomeStruct*) au8Data;
4
   if (ptr->element == 0) {
5
      ...
6
   }
7
}

Wenn au8Data nicht auf einen ausgerichteten Block zeigt knallt es beim
Zugriff auf ptr->element. Mit oder ohne (packed). Dafür gibt es
aber beim gcc das __attribute__((aligned)).

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Jörg Wunsch wrote:
> sparc64 (aka. UltraSPARC)

Architekturen gibt es viele, die unaligned nicht unterstützen. Und es 
ist Aufgabe des Compilers dafür zu sorgen, dass die Zugriffe auf 
unaligned Daten geeignet ausgeführt werden.

> Nö.  Der packt die Strukturen einfach, wenn du dann drauf zugreifen
> willst, knallt es (unaligned trap).

Den Compiler würde ich als ungeeignet bezeichnen.

> Ja, aber die sind dann teurer, weil sie mehrere Buszyklen brauchen.
> So ist es ja letztlich auch bei IA32.

Die Buszyklen hast du zwangsläufig. Egal ob vom Compiler generiert, oder 
von der Hardware. Im letzteren Fall spart man Befehlszyklen und 
(theoretisch) hat man die Möglichkeit das ganze noch atomar zu 
gestalten. Außerdem kann die Hardware erkennen, ob der Zugriff 
tatsächlich unaligned ist und den optimalen Zugriff für den gegebenen 
Fall ausführen.

Gruß
Marcus

von Manuel S. (thymythos) Benutzerseite


Lesenswert?

Bisher (AVR 8-bit und C167 16-bit) hab ich auch immer die packed 
Variante für Parser verwendet, weil es im Code schön aussieht.

Vielleicht sollte man mal ein "best practise" Parser Tutorial schreiben. 
Das kommt grade bei µC denke ich sehr häufig vor.

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


Lesenswert?

let wrote:

> Anders sieht es beim Casten aus. Nach einer Datenübertragung liegen die
> Strukturen ja zunächst als Byte Array vor.

Kann natürlich sein, dass aus diesem Grunde alle XDR-artigen Codes,
die ich bislang gesehen habe, sich die internen Datenstrukturen aus
dem externen Octet-Stream ,,zu Fuß'' zusammen gebaut haben.

Ich müsste mal meine UltraSPARC wieder anwerfen um nachzusehen, ob
der GCC mir dort wirklich bei __attribute__((packed)) alls auf
Bytezugriffe umfummelt.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

let wrote:
>
1
> void processData(uint8_t* au8Data)
2
> {
3
>    struct SomeStruct* ptr = (struct SomeStruct*) au8Data;
4
>    if (ptr->element == 0) {
5
>       ...
6
>    }
7
> }
8
>
>
> Wenn au8Data nicht auf einen ausgerichteten Block zeigt knallt es beim
> Zugriff auf ptr->element. Mit oder ohne (packed).

Kann ich zumindest mit dem RealView Compiler (3.1) nicht nachvollziehen, 
wenn die Struktur korrekterweise als packed deklariert wurde.

> Dafür gibt es aber beim gcc das __attribute__((aligned)).

Das geht aber nur, wenn entweder die Struktur eine geeignete Größe hat 
(Vielfaches von 2,4,8 Bytes, je nach Prozessor). Sonst knallt es beim 
Zugriff auf die zweite Struktur in der Byte Folge.

Gruß
Marcus

von let (Gast)


Lesenswert?

Ja, das (aligned) hilft nur bedingt. Wenn mehrere Strukturen
hintereinander liegen hat man verloren. Dann hilft nur noch
ein memcpy() in einen ausgerichteten Block (oder eine Variable).

Aber was macht denn der RealView bei einem solchen Konstrukt
das ich früher auf einem x86 hemmungslos so hingeschrieben
hätte:
1
   switch (au8Data[0]) {
2
      case 123:
3
         processData(dptr+1);
4
         break;
5
   }

Hier kann au8Data noch so aligned sein, processData() bekommt
eine ungerade Adresse. Prüft der bei jedem Cast von einen
Datentyp < 4 Bytes auf einen größeren ob die Adresse durch vier
teilbar ist?
Das würde erklären weshalb die Jungs bei Keil in ihren Demos
mit casts nur so um sich werfen. Nur wie sieht es dann mit der
Effizienz aus? Nur für den Fall das der Programmierer ausnahmsweise
einmal weiß was er tut ;)

von Manuel S. (thymythos) Benutzerseite


Lesenswert?

Also beim C167 Compiler (Keil) funktioniert der Zugriff mit struct aber 
nicht mit cast.

Funktioniert:
1
#pragma pack(1)
2
typedef struct {
3
  char a;
4
  int  b;
5
} MyStruct;
6
#pragma pack()
7
8
void func(char* data) {
9
  MyStruct *ptr = (MyStruct)data;
10
  int bb = b;
11
}

Funktioniert nicht:
1
void func(char* data) {
2
  int bb = *((int*)(&data[1]));
3
}

von let (Gast)


Lesenswert?

Ups, Tippfehler. Ich wollte eigentlich die gleichen Variablennamen
verwenden wie im vorigen Beispiel und nicht 'dptr'.
1
   switch (au8Data[0]) {
2
      case 123:
3
         processData(au8Data+1);
4
         break;
5
   }

@thymythos
Du meinst es wahrscheinlich so:
1
void func(char* data) {
2
  MyStruct *ptr = (MyStruct)data;
3
  int bb = ptr->b;
4
}

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

let wrote:
> Ja, das (aligned) hilft nur bedingt. Wenn mehrere Strukturen
> hintereinander liegen hat man verloren. Dann hilft nur noch
> ein memcpy() in einen ausgerichteten Block (oder eine Variable).

Und auch der wird unter Umständen Byte-weise ausgeführt, da man ja aus 
einem Byte array liest. Damit hat man also nicht unbedingt an Effizienz 
gewonnen. Kommt darauf an, wie oft man hinterher auf die Struktur 
zugreift.

>
1
>    switch (au8Data[0]) {
2
>       case 123:
3
>          processData(au8Data+1);
4
>          break;
5
>    }
6
>

Mit der aufrufenden Funktion macht der gar nichts. Egal was der 
Parameter für einen Wert hat, es ist immer ein gültiger (uint8_t *). 
Damit ist dem Prototypen genüge getan. Fall erledigt.

> Prüft der bei jedem Cast von einen Datentyp < 4 Bytes auf einen
> größeren ob die Adresse durch vier teilbar ist?

Nein, warum auch? Wir haben die Struktur als packed deklariert, und 
damit dem Compiler gesagt, dass er sie beliebig im Speicher anlegen 
darf.
Anhand der statisch ermittelbaren Information wird der Compiler die 
bestmögliche Methode verwenden, auf die Daten zuzugreifen. Damit hat der 
Compiler seine Aufgabe erfüllt. Der Overhead einer Fallunterscheidung 
zur Laufzeit würde den Vorteil des im Einzelfall schnelleren Zugriffs 
wahrscheinlich zunichte machen.

> Nur wie sieht es dann mit der Effizienz aus? Nur für den Fall das der
> Programmierer ausnahmsweise einmal weiß was er tut ;)

Der Compiler erzeugt vorrangig eine korrekte Instruktionssequenz 
(abgesehen von Fehlern in der Implementierung). Die kann -- nachrangig 
-- nur so effizient sein, wie es die darunter liegende Maschine zulässt. 
Wenn wir (die Programmierer) es besser wissen, dann hätten wir die 
Struktur ja intelligenter aufbauen können.

Gruß
Marcus

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Marcus Harnisch wrote:
> Jörg Wunsch wrote:
>> sparc64 (aka. UltraSPARC)
>
> Architekturen gibt es viele, die unaligned nicht unterstützen. Und es
> ist Aufgabe des Compilers dafür zu sorgen, dass die Zugriffe auf
> unaligned Daten geeignet ausgeführt werden.
>
>> Nö.  Der packt die Strukturen einfach, wenn du dann drauf zugreifen
>> willst, knallt es (unaligned trap).
>
> Den Compiler würde ich als ungeeignet bezeichnen.

Ich würd nicht sagen "ungeeignet", ich würd sagen "Bug".

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


Lesenswert?

Johann L. wrote:

> Ich würd nicht sagen "ungeeignet", ich würd sagen "Bug".

Ein Bug wäre es nur, wenn das eine zugesicherte Eigenschaft ist, dass
auch __attribute__((packed))-Strukturen unabhängig vom misalignment
trotzdem zugreifbar sind.  Ich finde erst einmal keine derartige
Zusicherung in der GCC-Doku, ich finde aber auch keine Bemerkung, dass
es nicht funktionieren würde.

von let (Gast)


Lesenswert?

@mharnisch:
Da haben wir wohl aneinander vorbei geschrieben.
Also noch mal zusammenhängend:
1
uint8_t au8GlblRecBuffer[256];
2
struct SomeStruct {
3
   uint8_t  dummy;
4
   uint32_t element;
5
} __attribute__((packed));
6
7
void processData(uint8_t* au8Data)
8
{
9
   struct SomeStruct* ptr = (struct SomeStruct*) au8Data;
10
   if (ptr->element == 0) {
11
      ...
12
   }
13
}
14
15
...
16
17
   switch (au8GlblRecBuffer[0]) {
18
      case 123:
19
         processData(au8RecBuffer+1);
20
         break;
21
   }

'processData()' bekommt eine ungerade Adresse und damit ist auch
'ptr' ungerade. Ich würde mich beim arm-gcc nicht darauf verlassen
das da etwas sinnvolles herauskommt. Im Prinzip sind wir hier
ja wieder bei den Arrays von Strukturen.

Nachtrag : Ich lese gerade das (packed) die Alignment-Anforderung
für die gesamte Struktur aufhebt, also nicht nur das Padding verhindert.

Was ich meinte (und eigentlich haben will) ist folgendes:
1
struct SomeStruct {
2
   uint8_t  a;
3
   uint32_t b __attribute__((packed));
4
} __attribute__((aligned));

Diese Struktur ist 8 Bytes groß, zwischen 'a' und 'b' befinden
sich aber keine Füllbytes. Der Zugriff auf die Elemente ist
wesentlich effizienter da der Compiler jetzt mit Wortzugriffen
dabeigeht und mit shift+or sich die Elemente zusammenbaut.
Das geht natürlich nur wenn die Strukturen auf ausgerichteten
Adressen liegen.


Also gehe ich nochmal durch die lwip-Sourcen...

von let (Gast)


Lesenswert?

Ohne __attribute__((aligned)) im letzten Beispiel scheint eine
5-Bytes Struktur herauszukommen die dennoch an einer ausgerichteten
Adresse liegen muß. Das muß ich an einem Trace-fähigen Aufbau mal
näher untersuchen.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

let wrote:
> Also noch mal zusammenhängend:
>
> [...]
>
> Nachtrag : [...]
> Ich lese gerade das (packed) die Alignment-Anforderung
> für die gesamte Struktur aufhebt, also nicht nur das Padding verhindert.

Also diese Problematik ist klar, oder? Durch die Parameter Deklaration
wird dem Compiler ja explizit gesagt, dass er ungerade Adressen zu 
erwarten hat.

> Was ich meinte (und eigentlich haben will) ist folgendes:
>
1
> struct SomeStruct {
2
>    uint8_t  a;
3
>    uint32_t b __attribute__((packed));
4
> } __attribute__((aligned));
5
>
>
> Diese Struktur ist 8 Bytes groß, zwischen 'a' und 'b' befinden
> sich aber keine Füllbytes. Der Zugriff auf die Elemente ist
> wesentlich effizienter da der Compiler jetzt mit Wortzugriffen
> dabeigeht und mit shift+or sich die Elemente zusammenbaut.
> Das geht natürlich nur wenn die Strukturen auf ausgerichteten
> Adressen liegen.

Ich hab's nicht ausprobiert, aber ich möchte das "wesentlich" 
bezweifeln.
Das Element b ist ja schließlich immer noch packed (alignment 1) und muß 
nach wie vor bei jedem Zugriff durch Einzelzugriffe zusammengebastelt 
werden. Ich fürchte, Du hast das Problem nur verschoben.

Nach dem ersten Zugriff wird der Compiler ohnehin versuchen, die 
Elemente in Registern zu halten. Wenn Du nicht gerade -O0 (oder 
volatile) verwendest ist das alles vermutlich nicht so tragisch, wie es 
erscheint.

Wenn Du dem Compiler nicht traust, dann könntest Du lokale Variablen für 
die Strukturelemente anlegen (oder eine lokale, ungepackte Version der 
Struktur erzeugen).

Gruß
Marcus

BTW: Ich hätte da einen Kurs anzubieten, der sich mit dem RealView 
Compiler beschäftigt :-)

von let (Gast)


Lesenswert?

Mal eine Reihe von Zugriffen (mit -O2 übersetzt):
1
char data[128];
2
struct ss {
3
  char a;
4
  int b __attribute__((packed));
5
  char c;
6
  short d __attribute__((packed));
7
  int e __attribute__((packed));
8
};// __attribute__((packed));
9
10
((struct ss*)data)->a = 113;
11
((struct ss*)data)->b = 114;
12
((struct ss*)data)->c = 115;
13
((struct ss*)data)->d = 116;
14
((struct ss*)data)->e = 117;

Ohne (packed) am Ende:
1
447:main.c        ****   ((struct ss*)data)->a = 113;
2
 546                 1 451 0
3
 547 0004 6CC89FE5     str  r3, [ip, #8]
4
 548 0008 7130A0E3     .loc 1 448 0
5
 549 000c 0030CCE5     str  r2, [ip, #0]
6
 448:main.c        ****   ((struct ss*)data)->b = 114;
7
 550                  r3, r3, #117
8
 551 0010 0020DCE5     .loc 1 449 0
9
 449:main.c        ****   ((struct ss*)data)->c = 115;
10
 450:main.c        ****   ((struct ss*)data)->d = 116;
11
 451:main.c        ****   ((struct ss*)data)->e = 117;
12
 552                ov  r2, #115
13
 553 0014 043083E2     .loc 1 448 0
14
 554                  strb  r3, [ip, #4]
15
 555 0018 722C82E3     .loc 1 449 0
16
 556                  strb  r2, [ip, #5]
17
 557 001c 08308CE5     .loc 1 493 0
18
 558                  mvn  r4, #-1073741824
19
 559 0020 00208CE5     sub  r4, r4, #12288
20
 560 0024 753043E2     ldr  r3, [r4, #-4095]
21
 561                  orr  r3, r3, #1
22
 562 0028 7320A0E3     str  r3, [r4, #-4095]

Mit (packed) am Ende:
1
 447:main.c        ****   ((struct ss*)data)->a = 113;
2
 544                oc 1 448 0
3
 545 0000 84389FE5     strb  r1, [r3, #1]
4
 546 0004 7120A0E3     .loc 1 449 0
5
 547                  strb  r2, [r3, #5]
6
 548 0008 F04F2DE9     .loc 1 448 0
7
 549                  mov  r0, #0
8
 448:main.c        ****   ((struct ss*)data)->b = 114;
9
 550                1 450 0
10
 551 000c 7210A0E3     add  r1, r1, #2
11
 552                  .loc 1 451 0
12
 553 0010 0020C3E5     add  r2, r2, #2
13
 449:main.c        ****   ((struct ss*)data)->c = 115;
14
 554                c 1 450 0
15
 555 0014 022082E2     strb  r1, [r3, #6]
16
 556                  .loc 1 451 0
17
 557 0018 0110C3E5     strb  r0, [r3, #11]
18
 558                  strb  r2, [r3, #8]
19
 559 001c 0520C3E5     .loc 1 448 0
20
 560                  strb  r0, [r3, #2]
21
 561 0020 0000A0E3     strb  r0, [r3, #3]
22
 450:main.c        ****   ((struct ss*)data)->d = 116;
23
 562                , [r3, #4]
24
 563 0024 021081E2     .loc 1 450 0
25
 451:main.c        ****   ((struct ss*)data)->e = 117;
26
 564                trb  r0, [r3, #7]
27
 565 0028 022082E2     .loc 1 451 0
28
 566                  strb  r0, [r3, #9]
29
 567 002c 0610C3E5     strb  r0, [r3, #10]
30
 568                  .loc 1 465 0
31
 569 0030 0B00C3E5     mvn  r4, #-1073741824
32
 570 0034 0820C3E5     sub  r4, r4, #12288
33
 571                  ldr  r3, [r4, #-4095]
34
 572 0038 0200C3E5     orr  r3, r3, #1
35
 573 003c 0300C3E5     str  r3, [r4, #-4095]

Wenn es also nur darum geht die Löcher innerhalb einer Struktur
zu stopfen, der Anfang der Struktur aber an einer ausgerichteten
Adresse liegt (ist ja oft der Fall), ist es besser nur die Elemente
der Struktur zu packen.

Durch diese Seite bin ich darauf gekommen es mal anders zu probieren:
http://digitalvampire.org/blog/index.php/2006/07/31/why-you-shouldnt-use-__attribute__packed/

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

let wrote:
> Wenn es also nur darum geht die Löcher innerhalb einer Struktur
> zu stopfen, der Anfang der Struktur aber an einer ausgerichteten
> Adresse liegt (ist ja oft der Fall), ist es besser nur die Elemente
> der Struktur zu packen.

Wenn man das sagen kann, klar. Aber es ging dem OP schließlich darum, 
dass "ein C Struct genau 3 Byte im Speicher belegt". Wenn Du einzelne 
Strukturelemente packst, hast Du möglicherweise immernoch ein Loch am 
Ende.

Fällt unter den Punkt "Wenn wir (die Programmierer) es besser wissen, 
dann hätten wir die Struktur ja intelligenter aufbauen können."

Nebenbei traue ich dem geposteten Code nicht. Erstmal ist er als 
C-Programm dargestellt unleserlich und zweitens fehlen Informationen 
(und komischerweise sogar Instruktionen, oder was soll "r3, r3, #117" 
bedeuten?).

Probier's mal mit gcc -O2 -S -c <file> -o <file>.S

Dann möchte ich auf (hüstel) den Abschnitt "Wichtige Regeln - erst 
lesen, dann posten!" hinweisen. Insbesondere auf die Stelle "(z.B. Code 
in anderen Sprachen)".

Drittens hast Du in beiden Fällen eine Struktur generiert, deren 
Elemente ein alignment von 1 haben. Damit wird das aligment der gesamten 
Struktur in beiden Fällen ebenfalls auf eins gesetzt, so dass das 
Speicherlayout gleich aussieht. Sowohl RealView 3.1, als auch 
CodeSourcery GCC 2008q3-39
erzeugen für die Zugriffe auf beide Strukturvarianten wie erwartet 
denselben Code.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Marcus Harnisch wrote:
> Sowohl RealView 3.1, als auch CodeSourcery GCC 2008q3-39
> erzeugen für die Zugriffe auf beide Strukturvarianten wie erwartet
> denselben Code.

Soll heißen:
RealView erzeugt für beide Fälle den selben Code, und GCC auch. RealView 
und GCC erzeugen natürlich voneinander unterschiedlichen Code.

--
Marcus

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch wrote:
> Johann L. wrote:
>
>> Ich würd nicht sagen "ungeeignet", ich würd sagen "Bug".
>
> Ein Bug wäre es nur, wenn das eine zugesicherte Eigenschaft ist, dass
> auch __attribute__((packed))-Strukturen unabhängig vom misalignment
> trotzdem zugreifbar sind.  Ich finde erst einmal keine derartige
> Zusicherung in der GCC-Doku, ich finde aber auch keine Bemerkung, dass
> es nicht funktionieren würde.

Stimmt, spezifiziert ist es nicht.

Gleichwohl kann GCC das prinzipiell. Und es ist immer netter, Code zu 
haben, der funktioniert, als solcher, der einem zur Laufzeit um die 
Ohren fliegt wegen einer Trap weil das Alignment bei einem Zugriff nicht 
stimmte.

GCC weiß auch, wie man Bitfelder auseinanderbröseln/zusammensetzen muss, 
die über Bytegrenzen hinweg gehen. Die entsprechenden Pattern sind 
immerhin Pflichtpattern in der Maschinenbeschreibung (insv, extv).

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


Lesenswert?

Weißt du zufällig auch, ob das Feature in der testsuite überprüft wird?
In dem Falle sollte es dann auch in der Doku erwähnt werden, ab dann
darf man sich drauf verlassen.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

Johann L. wrote:
> Jörg Wunsch wrote:
>> Ein Bug wäre es nur, wenn das eine zugesicherte Eigenschaft ist, dass
>> auch __attribute__((packed))-Strukturen unabhängig vom misalignment
>> trotzdem zugreifbar sind.  Ich finde erst einmal keine derartige
>> Zusicherung in der GCC-Doku, ich finde aber auch keine Bemerkung, dass
>> es nicht funktionieren würde.
>
> Stimmt, spezifiziert ist es nicht.

Es wäre schon besser wenn sich alle Compiler an das ABI für die 
jeweilige Architektur halten würden, und dafür funktionierenden Code 
erzeugen. Zum Beispiel sind Strukturlayout und -alignment für ARM 
Prozessoren im AAPCS "4.3 Composite Types" erklärt. Für andere 
Prozessoren ist das sicher ähnlich.

Das selbe gilt für Dinge wie Bit-fields ("7.1.7 Bit-fields"), etc.

Gruß
Marcus

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


Lesenswert?

Marcus Harnisch wrote:

> Für andere
> Prozessoren ist das sicher ähnlich.

Sowas kenne ich praktisch nur für RISC-CPUs.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Jörg Wunsch wrote:
> Weißt du zufällig auch, ob das Feature in der testsuite überprüft wird?
> In dem Falle sollte es dann auch in der Doku erwähnt werden, ab dann
> darf man sich drauf verlassen.

__attribute__((packed)) wird in der Testsuite hier und da behandelt und 
ist in einigen  C-Dateien drinne, etwa in

./gcc/testsuite/gcc.c-torture/execute/20010518-2.c

http://gcc.gnu.org/viewcvs/trunk/gcc/testsuite/gcc.c-torture/execute/20010518-2.c?revision=138078&view=markup

Ich hab aber nicht den Überblick über alle Testprogramme, das hier war 
einfach eines ausm grep.

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


Lesenswert?

Johann L. wrote:

> ./gcc/testsuite/gcc.c-torture/execute/20010518-2.c

Das sieht gut aus, da wird das wirklich explizit getestet.  Wenn ich
dran denke, werde ich mal eine Patch für die Doku bauen und versuchen,
durch die GCC-Patch-Queue zu prügeln.

von let (Gast)


Angehängte Dateien:

Lesenswert?

O.k, o.k, ich habe das nur schnell eben hingeschmiert.
Nochmal etwas ordentlicher:
1
char data[128];
2
struct ss
3
{
4
   char a;
5
   int  b;// __attribute__((packed));
6
};// __attribute__((packed));
7
8
int main()
9
{
10
   ((struct ss*)data)->b = 123;
11
12
   return 0;
13
}

Compiliert wurde mit
arm-elf-gcc -O2 -mcpu=arm7tdmi-s -S -c struct.c -o struct.s

Und hier die Ergebnisse:

Ganz ohne Attribut
1
  ldr  r3, .L3
2
  mov  r2, #123
3
  mov  r0, #0
4
  str  r2, [r3, #4]
5
  bx  lr
6
.L4:
7
  .align  2
8
.L3:
9
  .word  data
10
  .size  main, .-main
11
  .comm  data,128,1

Mit (packed) für 'b':
1
  ldr  r3, .L3
2
  ldrb  r2, [r3, #0]  @ zero_extendqisi2
3
  mov  r0, #0
4
  orr  r2, r2, #31488
5
  str  r2, [r3, #0]
6
  strb  r0, [r3, #4]
7
  bx  lr
8
.L4:
9
  .align  2
10
.L3:
11
  .word  data
12
  .size  main, .-main
13
  .comm  data,128,1

Mit beiden (packed) Attributen:
1
  mov  r0, #0
2
  ldr  r3, .L3
3
  orr  r2, r0, #123
4
  strb  r2, [r3, #1]
5
  strb  r0, [r3, #4]
6
  strb  r0, [r3, #2]
7
  strb  r0, [r3, #3]
8
  bx  lr
9
.L4:
10
  .align  2
11
.L3:
12
  .word  data
13
  .size  main, .-main
14
  .comm  data,128,1

Den Quellcode sowie die komplette Ausgabe des Compilers habe
ich angehängt.

von Marcus H. (mharnisch) Benutzerseite


Lesenswert?

let wrote:
> O.k, o.k, ich habe das nur schnell eben hingeschmiert.
> Nochmal etwas ordentlicher:

Vielen Dank :-)

Zunächst mal ist es wichtig, daran zu denken, dass Du immer nur auf das 
erste Element zugreifst, und nicht auf beliebige Elemente des Arrays. 
Externe Deklaration der Variablen kann das Bild verändern.

> Ganz ohne Attribut
>
1
>   ldr  r3, .L3
2
>   mov  r2, #123
3
>   mov  r0, #0
4
>   str  r2, [r3, #4]
5
>   bx  lr
6
> .L4:
7
>   .align  2
8
> .L3:
9
>   .word  data
10
>   .size  main, .-main
11
>   .comm  data,128,1
12
>

Das knallt erwartungsgemäß (bei ARMv4/5), wenn data ein alignment != 4 
hat.
Die .comm Direktive gibt sogar explizit ein Alignment des ersten 
Elements von 1 vor, oder? Kann ein GCC Kenner das beurteilen?

Ist aber ein klarer Programmierfehler. Wir haben den Compiler getäuscht.

> Mit (packed) für 'b':
>
1
>   ldr  r3, .L3
2
>   ldrb  r2, [r3, #0]  @ zero_extendqisi2
3
>   mov  r0, #0
4
>   orr  r2, r2, #31488
5
>   str  r2, [r3, #0]
6
>   strb  r0, [r3, #4]
7
>   bx  lr
8
> .L4:
9
>   .align  2
10
> .L3:
11
>   .word  data
12
>   .size  main, .-main
13
>   .comm  data,128,1
14
>

Das knallt auch. Hier "optimiert" der Compiler, indem er ebenfalls 
annimmt, dass data ein alignment von vier Bytes hat. Allerdings wird 
data hier wieder mit einem alignment von 1 angelegt.

> Mit beiden (packed) Attributen:
>
1
>   mov  r0, #0
2
>   ldr  r3, .L3
3
>   orr  r2, r0, #123
4
>   strb  r2, [r3, #1]
5
>   strb  r0, [r3, #4]
6
>   strb  r0, [r3, #2]
7
>   strb  r0, [r3, #3]
8
>   bx  lr
9
> .L4:
10
>   .align  2
11
> .L3:
12
>   .word  data
13
>   .size  main, .-main
14
>   .comm  data,128,1
15
>

Die einzig sichere Methode für diesen Fall. Etwas weniger effizient 
zwar, dafür in jedem Fall korrekt.

Es ist mir völlig unklar, warum Deine GCC Version in den letzten beiden 
Fällen unterschiedlichen Code erzeugt. Meiner Meinung nach, müsste in 
beiden Fällen der selbe Code rauskommen, was durch zwei verschiedene 
Compiler (einer davon ein neuerer GCC) bestätigt wird.

Gruß
Marcus

von let (Gast)


Lesenswert?

Jetzt habe ich den Test nochmal mit Codesourcery wiederholt.
Da kommt auch bei mir für die beiden letzten Fälle der
gleiche Code raus.
Der andere GCC stammt aus dem Yagarto Paket (v4.3.2).

Und da ich jetzt neugierig geworden bin habe ich noch
den WinARM-4.3.0 probiert. Der verhält sich wie der Codesourcery.

Aber Danke für deine Erklärungen. Da habe ich (wieder mal)
Erkenntisse gewonnen von denen ich dachte das ich sie
schon hätte ;)

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.