Hallo zusammen.
Ich habe grade ein paar Verstaendnisprobleme was das alignen vom GCC
angeht.
folgender C code ist betroffen:
1
charmsg[12];
2
((uint32_t*)msg)[0]=0x02;
3
((uint32_t*)msg)[1]=0x00;
4
((uint32_t*)msg)[2]=0x00;
Das ganze funktioniert ansich, aber generiert folgende warnung:
1
error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]
2
((uint32_t *)msg)[0] = 0x02;
Aber nur fuer den ersten Zugriff! Wenn es zu Folgendem aendere laeuft
der GCC so durch.
1
charmsg[12];
2
msg[0]=0x02;
3
msg[1]=msg[2]=msg[3]=0x00;
4
((uint32_t*)msg)[1]=0x00;
5
((uint32_t*)msg)[2]=0x00;
Koennte mir da jemand auf die Spruenge helfen?
Wenn es eine struct {..} waere wuerde ich ja verstehen, dass der
compiler versucht irgendwas zu alignen. Aber in hier stehe ich auf dem
Schlauch.
Einfach einen 32Bit-Pointer loest das problem natuerlich, daran hatte
ich garnicht gedacht!
Mich wundert es aber, dass der GCC nur bei der Adressierung der ersten
4Bytes meckert.
es könnte daran liegen, dass eigentlich deine Bytes nicht im uC oder PC
"gepackt" gespeichert sind. Es sind zwischen deine Chars immer
Zwischenräume frei, also unbenutzter Speicherplatz. Das liegt am
Compiler, da er so schneller auf die einzelnen Elemente zugreifen kann.
Du brauchst für dein Beispiel eigentlich eine "pragma pack" Anweisung.
Ich hoffe, ich lieg jetzt nicht ganz daneben.
Flo schrieb:> Wenn es eine struct {..} waere wuerde ich ja verstehen, dass der> compiler versucht irgendwas zu alignen.
Vielleicht ist das Gegenteil das Problem. char braucht kein Alignment,
aber uint32_t ggf. schon.
lulu schrieb:> es könnte daran liegen, dass eigentlich deine Bytes nicht im uC oder PC> "gepackt" gespeichert sind.
Nein. Arrays sind immer "gepackt".
Nebei wirft gcc ein error und bricht damit (in der Standarteinszellung)
ab. Daher gibt es keine weiteren Fehler. Aber ein lauffähiges Programm
hast du dami sicher nicht bekommen.
Ansonsten schließ ich mich meinen Vorpostern an, was die Lösung angeht.
Wobei du ehr ein uint32_array erzeugenden solltest oder mit dem
richtigen attribute versehen, dass es an die richtige Bytegrenze gelinkt
wird.
Näheres verrät die Doku zum GCC
Je nach Compiler, Hardware etc. bekommst du als Ergebnis von f dann 2,
3 oder 5 oder ne Hardware-Trap falls der Compiler vorher nicht schon mit
einem Fehler abbrach.
Und der Zeiger-Cast von oben ändert nix daran.
Florian Otte schrieb:> Einfach einen 32Bit-Pointer loest das problem natuerlich, daran hatte> ich garnicht gedacht!
Nein. Er verschleiert es nur.
Nimm memcpy. Dafür ist es da.
Stefan schrieb:> Nebei wirft gcc ein error und bricht damit (in der Standarteinszellung)> ab. Daher gibt es keine weiteren Fehler. Aber ein lauffähiges Programm> hast du dami sicher nicht bekommen.> Ansonsten schließ ich mich meinen Vorpostern an, was die Lösung angeht.> Wobei du ehr ein uint32_array erzeugenden solltest oder mit dem> richtigen attribute versehen, dass es an die richtige Bytegrenze gelinkt> wird.> Näheres verrät die Doku zum GCC
Falsch. Minimalbeispiel ist angehaengt!
DirkB schrieb:> Florian Otte schrieb:>> Einfach einen 32Bit-Pointer loest das problem natuerlich, daran hatte>> ich garnicht gedacht!>> Nein. Er verschleiert es nur.>> Nimm memcpy. Dafür ist es da.
Jain. Hatte mich nicht ganz richtig ausgedrueckt: ich kann auch direkt
ein array ueber uint32 aufbauen, damit gehts dann ohne probleme....
also
1
uint32_tmsg[3];
In die anderen Antworten kann ich leider erst morgen reinschauen.
Aber vielen dank fuer die anregungen bisher!
Florian Otte schrieb:> Stefan schrieb:>> Nebei wirft gcc ein error und bricht damit (in der Standarteinszellung)>> ab. Daher gibt es keine weiteren Fehler. Aber ein lauffähiges Programm>> hast du dami sicher nicht bekommen.....>> Näheres verrät die Doku zum GCC>> Falsch. Minimalbeispiel ist angehaengt!
Standarteinstellung verstehe ich als:
1
gcc main.c
. Damit läuft der Compiler mit Warnung durch. Kein Abbruch (-Werror
fehlt ja...) und vor allem ist das Programm lauffähig und tut genau das,
was es soll! Es baut UDP Pakete und verschickt diese, das ganze in
Wireshark angeschaut: Es passt (zumindest auf meiner Architektur 64bit
gcc... etc).
!!Die Warnung kommt nur beim ersten Zugriff!!
Nicht beim ersten auftreten eines Zugriffs sondern bei dem versuch die
erste Stelle zu dereferenzieren!
Also wenn ich nur die erste Zeile ändere dann läuft das ganze ohne
Fehler/Warnungen durch!
Wenn es ein alignment Problem gäbe, dann müsste alles falsch alignes
sein und nicht nur die daten an der (falsch) gecasteten ersten Stelle,
oder?
Vielleicht habe ich mich falsch ausgedrückt: Warum meckert er nur bei
der ersten Stelle? Das geht mir nicht in den Kopf...
1
charmsg[12];
2
msg[0]=0x02;// Zugriff auf char, kein problem
3
msg[1]=msg[2]=msg[3]=0x00;// " " " " "
4
((uint32_t*)msg)[1]=0x00;// Casten des msg pointer auf 4Byte Typ
5
((uint32_t*)msg)[2]=0x00;// Deref. der 2., 3. usw stelle kein
Florian Otte schrieb:> Wenn es ein alignment Problem gäbe, dann müsste alles falsch alignes> sein und nicht nur die daten an der (falsch) gecasteten ersten Stelle,> oder?
Es geht hier nicht um Alignment, sondern um Aliasing. Aliasing heißt,
dass ein und dasselbe Objekt über mehrerer Namen erreichbar ist. Das
kann bei der Optimierung Probleme machen, wenn der Compiler versucht,
aus Effizienzgründen die Ausführungsreihenfolge von Anweisung zu ändern
(aka reordering).
Beispiel:
1
foo(i,i++);
ist in C/C++ bekanntlich undefiniert, da die Auswertungsreihenfolge der
Parameter explizit undefiniert und folglich nicht klar ist, ob als
erster Parameter i oder i+1 übergeben wird.
1
foo(i,j++);
ist OK (zumindest in C (*)),
1
foo(*i,*j++);
dagegen nur, solange die Pointer auf unterschiedliche Objekte zeigt,
also i != j gilt. ((*) in C++ könnte i auch als
1
intconst&i=j;
definiert sein und umgekehrt, daher die Einschränkung.)
Wenn der Compiler nun zur Optimierung die an sich wohldefinierte
Ausführungsreihenfolge von Anweisungen zu ändern versucht hat er ein
ganz ähnliches Problem. Er darf das nur, wenn die Semantik des Programms
unverändert bleibt. Dazu muss er eventuelles Aliasing im Auge behalten
können. Bei Casten vom Pointern inkompatibler Typen kann er das nicht
mehr (garantieren), es ist daher undefiniertes Verhalten, daher die
Warnung.
PS: Bei char* gibt es eine Ausnahme: Hier macht der Compiler per
Definition immer pessimistische Annahmen betreffs Aliasing, daher könnte
Punkt 2 oben funktionieren.
PPS: Aliasing ist nur ein potentielles Problem bei Casten von Pointern
unterschiedlichen Typs. Alignment und Bytefolgeprobleme kommen noch
dazu. Daher würde ich – wie gesagt – immer Variante 4 (siehe oben)
empfehlen.
Florian Otte schrieb:> Also wenn ich nur die erste Zeile ändere dann läuft das ganze ohne> Fehler/Warnungen durch!
Und was beweist das? Mit Dijskras Worten:
"Testing shows the presence, not the absence of bugs."
Johann L. schrieb:> Florian Otte schrieb:>> Also wenn ich nur die erste Zeile ändere dann läuft das ganze ohne>> Fehler/Warnungen durch!>> Und was beweist das? Mit Dijskras Worten:>> "Testing shows the presence, not the absence of bugs."
Mir scheint es verwunderlich, dass der compiler nur an einer stelle
meckerte
Das soll offensichtlich garnichts beweisen, denn wie du sehen kannst
steht nirgendswo etwas von "Das zeigt..." "Das beweist.." oder sonst
etwas womit ich hätte zeigen wollen, dass das etwas beweist...
@ A. H. (ah8):
Ich habe offensichtlich die Begrifflichkeiten verwechselt - mein Fehler!
Anstatt mich nun länger zu fragen warum er ein aliasing problem bei der
ersten stelle und nicht irgendeiner anderen stelle sieht, nehm ich nun
direkt 32bit arrays. Vielen Dank für die aliasing Hinweise! Das werde
ich mir nachher nochmal genauer anschaun.
> Nicht beim ersten auftreten eines Zugriffs sondern bei dem versuch die> erste Stelle zu dereferenzieren!
Das liegt daran, dass lesende Zugriff bezüglich Aliasing unkritisch
sind. Erst wenn ein Objekt verändert wird, hast Du ein potentielles
Problem.
Florian Otte schrieb:> Anstatt mich nun länger zu fragen warum er ein aliasing problem bei der> ersten stelle und nicht irgendeiner anderen stelle sieht,
Versuch doch mal des Spaßes halber den folgenden Code zu compilieren:
1
charmsg[12];
2
msg[0]=0x02;
3
msg[1]=msg[2]=msg[3]=0x00;
4
((uint32_t*)msg)[0]=0x00;
5
((uint32_t*)msg)[2]=0x00;
Ich könnte mir vorstellen, dass die Adressrechnung Einfluss auf die
Annahmen des Compilers bezüglich Aliasing hat, das also msg[0] letztlich
zu *msg vereinfacht und folglich anders behandelt wird als *(msg+1);
> nehm ich nun> direkt 32bit arrays. Vielen Dank für die aliasing Hinweise! Das werde> ich mir nachher nochmal genauer anschaun.
Mach's von Hand! :-)