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)...
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.
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
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!
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?
NochJemand schrieb: > typedef struct{ ... > }Str; > > &((&Str)->m5) liefert z.B. 0x20000011 (Str+13) Variablen haben Adressen, Typen nicht. &Str ist Unfug.
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.
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.
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.
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.
@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.
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.
Plattform/Compiler/Optionen haben wir jetzt. Fehlt noch der compilierbare Quelltext mit dem isolierten Problem, so dass wir dein Problem reproduzieren können?
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.
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.
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.
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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.