Forum: Compiler & IDEs GCC struct member unterschiedliche Addressen


von VerstehtDieWeltNichtMehr (Gast)


Lesenswert?

Ein struct member bekommt je nachdem, wo man ihn referenziert 
unterschiedliche Adressen, d.h. &struct.member ergibt an einer Stelle 
einen anderen Offset von &struct als an anderen. Das struct hat dabei 
ein festgelegtes Alignment & packaging. Compiliert mit GCC 4.9 für ARM 
Cortex M4. Wie kann das sein?

Der Debugger in Eclipse hat dabei noch eine weitere Sonderlichkeit: 
&struct.member liefert bei den "Expressions" ein anderes Ergebnis als 
&(struct.member)...

von Peter II (Gast)


Lesenswert?

VerstehtDieWeltNichtMehr schrieb:
> &struct.member ergibt an einer Stelle
> einen anderen Offset von &struct als an anderen.

innerhalb des gleichen Projektes sollte das nicht der Fall sein.

von (prx) A. K. (prx)


Lesenswert?

So abstrakt betrachtet wirst du nicht weiter kommen als bis zu 
allgemeinen philosophischen Betrachtungen. Es wird schon etwas realer 
werden müssen. Besonders, wenn es am Ende beispielsweise drauf 
rausläuft, dass dich der Optimizer oder der Debugger narrt.

Soll heissen: Testcode nötig, der den Effekt reproduziert.

: Bearbeitet durch User
von NochJemand (Gast)


Lesenswert?

Bei meiner Suche bin ich auf diesen Thread gestoßen:
Ich habe dasselbe Problem.

Beispiel:
typedef struct{
  struct s1 *m1;
  struct s2 *m2;
  uint32_t m3;
  enum{
    Low = 0, High
  }m4;
  uint32_t m5;
}Str;

&((&Str)->m5) liefert z.B. 0x20000011 (Str+13)

Aber in
void f(Str* s)
{
    uint32_t m5 = s->m5;
    uint32_t *pm5 = &(s->m5);
}
kommt etwas anderes heraus, pm5 ist dabei z.B. 0x20000014 (Str+16).
Übersehe ich gerade etwas grundlegendes?
Danke!

von NochJemand (Gast)


Lesenswert?

Nachtrag: Es scheint mir extrem wahrscheinlich, dass es ausschließlich 
die Größe des enums ist, die das ganze auslößt. Wenn ich ein Element mit 
einem ausreichend großem Wert hinzufüge kommen beide Fälle zum selben 
Ergebnis.
Das dürfte doch nicht passieren?

von (prx) A. K. (prx)


Lesenswert?

NochJemand schrieb:
> typedef struct{
...
> }Str;
>
> &((&Str)->m5) liefert z.B. 0x20000011 (Str+13)

Variablen haben Adressen, Typen nicht. &Str ist Unfug.

von (prx) A. K. (prx)


Lesenswert?

NochJemand schrieb:
> Nachtrag: Es scheint mir extrem wahrscheinlich, dass es ausschließlich
> die Größe des enums ist, die das ganze auslößt. Wenn ich ein Element mit
> einem ausreichend großem Wert hinzufüge kommen beide Fälle zum selben
> Ergebnis.

enum { A = 1 } und enum { A = 1000 } können unterschiedlich gross sein, 
müssen es aber nicht. Je nach Compiler und im jeweiligen Kontext 
verwendeter Compiler-Optionen.

von Jim M. (turboj)


Lesenswert?

NochJemand schrieb:
> &((&Str)->m5) liefert z.B. 0x20000011 (Str+13)

Ein uint32_t an einer ungraden Addresse? Du machst was falsch. Der 
Compiler würde das an Addresse 0x14 tun - Stichwort Alignment.

Für Strukturmember gibts doch das offsetof: 
https://gcc.gnu.org/onlinedocs/gcc/Offsetof.html

Bei Headern muss man manchmal auf Fießheiten wie #pragma pack(1) 
aufpassen.

von Mikro 7. (mikro77)


Lesenswert?

NochJemand schrieb:
> Ich habe dasselbe Problem.

Isoliere doch mal das Verhalten in einem kleinen kompilierbaren Programm 
und sag uns welche Plattform und welchen Compiler du verwendest, so dass 
wir das Problem reproduzieren können.

Grundsätzlich sind Offset 13 und Offset 16 möglich. Imo allerdings nicht 
an zwei unterschiedlichen Stellen im gleichen Programm, die sich auf die 
selbe Definition beziehen.

von Markus F. (mfro)


Lesenswert?

NochJemand schrieb:
> Übersehe ich gerade etwas grundlegendes?

Ich denke schon.

Du hast an einer Stelle der Struktur das Attribut 
__attribute__((packed)) verpaßt und an einer anderen nicht.

von NochJemand (Gast)


Lesenswert?

@A.K.
Tut mir Leid, war schon etwas spät. Natürlich meine ich "str" mit einem 
global definiertem "Str str;".
Dass das Enum unterschiedlich groß sein kann ist klar, aber dass es im 
selben Programm mit derselben Definition unterschiedlich groß ist 
wundert mich dann doch.

Die Parameter sind wie oben: GCC 4.9.2 auf ARM Cortex M4F
Complier-Parameter: arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb 
-mfloat-abi=hard -mfpu=fpv4-sp-d16 -Og -fmessage-length=0 -fsigned-char 
-ffunction-sections -fdata-sections -fsingle-precision-constant -Wall

Natürlich im selben Programm, dass das Alignement und Packaging in 
verschiedenen Programmen unterschiedlich sein kann ist klar.

Ich erwarte, dass zumindest der Funktionszeiger auf jeden Fall aligned 
ist. Das ist aber nicht immer der Fall - in der Funktion wurde er auch 
unaligned (d.h. zu 0x800, also die beiden LSB wurden abgeschnitten und 
auf das nächste Element geschoben).

Um die Frage also nocheinmal klar neu zu stellen:
Muss "&(str.m5)", "&((&str)->m5)" und in der Funktion "f" "&(s->m5)" mit 
"Str str;" im selben Programm unter Verwendung derselben Definition 
immer dasselbe Offset von "&str" ergeben?
Ich hatte das Problem auch mit explizitem Packaging und Alignment.
Meiner Meinung nach: ja.

von NochJemand (Gast)


Lesenswert?

Ach ja, vergessen: Der Compiler-Parameter -mno-unaligned-access ändert 
nichts.
Der Compiler stuckt eine Warning aus, das zu m5 ein Padding eingefügt 
wurde.
Komischerweise kommt das aber nur in "f" zum Tragen, nicht im globalen 
Kontext.

von Mikro 7. (mikro77)


Lesenswert?

Plattform/Compiler/Optionen haben wir jetzt. Fehlt noch der 
compilierbare Quelltext mit dem isolierten Problem, so dass wir dein 
Problem reproduzieren können?

von NochJemand (Gast)


Lesenswert?

Danke für die Antworten!
Mittlerweile habe ich das Problem gefunden...
In einem Header aus 3rd party Code, den ich leider verwenden muss, gibt 
es hunderte Definitionen von structs. Fast alle sind mit einem
"#pragma pack (push, 1)" und "#pragma pack(pop)" eingerahmt, bzw. 
zumindest blockweise. In einem solchen Block aus zig Definitionen steht 
mitten drin noch ein "push" - dieser Mist hat mich Stunden gekostet.


@Jim Meba:
Das ist nicht das Problem - natürlich hat es einen Performaceverlust zur 
Folge, weil der Compiler jeden Zugriff auf einzelne Bytes auflöst und 
daraus den Wert zusammensetzt, aber funktional sollte das kein Problem 
sein.

von Rolf M. (rmagnus)


Lesenswert?

NochJemand schrieb:
> @Jim Meba:
> Das ist nicht das Problem - natürlich hat es einen Performaceverlust zur
> Folge, weil der Compiler jeden Zugriff auf einzelne Bytes auflöst und
> daraus den Wert zusammensetzt, aber funktional sollte das kein Problem
> sein.

Wenn er das denn tut und nicht stattdessen einfach eine CPU-Exception 
wegen ungültigem Zugriff ausgelöst wird.

von NochJemand (Gast)


Lesenswert?

Bitte korrigiert mich wenn das nicht stimmt:
Soweit ich das verstehe kann das nur passieren, wenn der Compiler von 
einem mindestens so großen Alignment ausgeht wie der Ziel-Datentyp 
erfordern würde, es aber nicht so bekommt. (Und ihm unaligned Zugriffe 
verboten wurden...)

Beispiel:
uint8_t rxB[9] = {};
uint8_t *rxD = readReg(0x01, 0, rxB, 8);
uint64_t a = *((uint64_t*)&rxB[1]);
uint64_t b = *((uint64_t*)rxD);

Die Zuweisung zu a funktioniert - der Compiler macht daraus z.B.:
ldrb.w  r2, [sp, #29]
ldrb.w  r3, [sp, #30]
orr.w   r2, r2, r3, lsl #8
ldrb.w  r3, [sp, #31]
orr.w   r2, r2, r3, lsl #16
ldrb.w  r3, [sp, #32]
orr.w   r2, r2, r3, lsl #24
ldrb.w  r3, [sp, #33]   ; 0x21
ldrb.w  r1, [sp, #34]   ; 0x22
orr.w   r3, r3, r1, lsl #8
ldrb.w  r1, [sp, #35]   ; 0x23
orr.w   r3, r3, r1, lsl #16
ldrb.w  r1, [sp, #36]   ; 0x24
orr.w   r3, r3, r1, lsl #24
-> Ihm ist das Alignement von rxB bekannt - nämlich 1. Damit muss er den 
Wert Byte-weise zsammensetzen.

Wogegen die Zuweisung zu b übersetzt wird zu:
ldrd    r2, r3, [r0]
-> Der Cast von uint8_t* mit unbekanntem Alignment zu uint64_t* führt 
zur Annahme, dass das Alignment für uint64_t passt, also auf einer durch 
8 teilbaren Adresse liegt. Das führt dann zu einer Exception.

von Mikro 7. (mikro77)


Lesenswert?

Das ist "undefined behavior". Kann jeder Compiler machen wie er will.
1
6.3.2.3 Pointers
2
A pointer to an object type may be converted to a pointer to 
3
a different object type. If the resulting pointer is not correctly 
4
aligned for the referenced type, the behavior is undefined.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Rolf M. schrieb:
> NochJemand schrieb:
>> @Jim Meba:
>> Das ist nicht das Problem - natürlich hat es einen Performaceverlust zur
>> Folge, weil der Compiler jeden Zugriff auf einzelne Bytes auflöst und
>> daraus den Wert zusammensetzt, aber funktional sollte das kein Problem
>> sein.
>
> Wenn er das denn tut und nicht stattdessen einfach eine CPU-Exception
> wegen ungültigem Zugriff ausgelöst wird.

Der Zugriff auf die Komponente eines packed Objekts ist doch kein 
Undefined Behavior?

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.