Forum: Compiler & IDEs Warnung / Fehler nach casten von char a[] zu uint_32*


von Flo (Gast)


Lesenswert?

Hallo zusammen.

Ich habe grade ein paar Verstaendnisprobleme was das alignen vom GCC 
angeht.

folgender C code ist betroffen:
1
char msg[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
char msg[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.

von Florian O. (simuru)


Lesenswert?

So jetzt nochmal angemeldet mit einem Minimalbeispiel:
1
/* File main.c */
2
#include <inttypes.h>
3
#include <stdio.h>
4
5
int main( void ) {
6
   char msg[12];
7
   ((uint32_t *) msg)[0] = 0x02;
8
   ((uint32_t *) msg)[1] = 0x00;
9
   ((uint32_t *) msg)[2] = 0x00;
10
   printf("%d", msg[0]);
11
   return 0;
12
}
1
gcc -O2 -Wall -Werror main.c
2
3
main.c:8:4: error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing]
4
    ((uint32_t *) msg)[0] = 0x02;

von lulu (Gast)


Lesenswert?

Hi,
was willst du damit bezwecken?

char = 8 bit
uint32_t = 32 bit

ich würde hier eher sowas empfehlen:
1
char msg[12];
2
uint32_t *msg32 = (uin32_t*)msg;
3
msg32[i] = ...;

von Florian O. (simuru)


Lesenswert?

1
((uint32_t *) msg)[0] = 0x02;

ist netter zu lesen als:
1
msg[0] = 0x02;
2
msg[1] = msg[2] = msg[3] = 0x00;

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.

von lulu (Gast)


Lesenswert?

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.

von Rolf Magnus (Gast)


Lesenswert?

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".

von lulu (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Nein. Arrays sind immer "gepackt".

Dann lag ich daneben ;-)

von Stefan (Gast)


Lesenswert?

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

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Nehmen wir mal folgendes kleines Beispiel:
1
#include <stdint.h>
2
3
char f (void)
4
{
5
    char msg[12];
6
    msg[1] = 5; 
7
    ((uint32_t *) msg)[0] = 0x01020304;
8
    return msg[1];
9
}

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.

von DirkB (Gast)


Lesenswert?

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.

von A. H. (ah8)


Lesenswert?

Meiner Meinung nach hast du vier Möglichkeiten, das Problem zu lösen 
(alle ungetestet):

1. nimm eine union:
1
union {
2
  uint32_t data[3];
3
  char msg[sizeof(data)];
4
};

2. caste in die umgekehrte Richtung:
1
uint32_t data[3];
2
char *msg = (char*) data;

3. nimm memcpy (wie schon erwähnt):
1
uint32_t data[3];
2
char msg[sizeof(data)];
3
memcpy(msg, data, sizeof(msg));

4. mach es von Hand:
1
uint32_t data[3];
2
char msg[sizeof(data)];
3
for ( int i=0; i<sizeof(data)/sizeof(*data); ++i )
4
  for ( int j = 0; j < sizeof(*data); ++j )
5
    msg[i*sizeof(*data)+j] = data[i] >> j*8 & 0xff;

Ich persönlich würde immer die letzte Variante nehmen (die man sicher 
noch optimieren kann), da nur sie Dir volle Kontrolle gibt, was wo 
hingeht.

von Florian O. (simuru)


Lesenswert?

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_t msg[3];


In die anderen Antworten kann ich leider erst morgen reinschauen.
Aber vielen dank fuer die anregungen bisher!

von Florian O. (simuru)


Lesenswert?

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
char msg[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
6
                                    // Problem!

von A. H. (ah8)


Lesenswert?

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
int const& 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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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."

von Florian O. (simuru)


Lesenswert?

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.

von A. H. (ah8)


Lesenswert?

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

von A. H. (ah8)


Lesenswert?

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
char msg[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! :-)

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.