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.
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:> 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.
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?