Forum: Compiler & IDEs Ist das ein Bug im Compiler?


von Wolfgang K. (opendcc)


Lesenswert?

Hallo,

ich habe eine Stelle, die ich mir nicht erkären kann. Ev. ist es ein Bug 
im AVR GCC?
1
void dmx_direct_stepup_fail(unsigned char *validmap)
2
  {
3
    unsigned char d;  // dmx-channel
4
    unsigned char mask = 1;
5
6
    for (d=0; d<NUM_OF_DMX; d++)
7
      {
8
        if (*validmap & mask)
9
          {
10
            if (HIGH8(lightdimm[d].volume) == 255);                             // max
11
            else
12
              {
13
                lightdimm[d].volume += 256; 
14
                lightdimm[d].target = HIGH8(lightdimm[d].volume);
15
                dmx_value[d] = HIGH8(lightdimm[d].volume);
16
              }
17
          }
18
        mask <<= 1;
19
        if (!mask)
20
          {
21
            mask = 1;
22
            validmap++;
23
          }
24
      }
25
    flag_set(F_update_grayscale);
26
  }
27
28
mit
29
30
#define HIGH8(X) (((t_data16*)&(X))->high8)
31
#define LOW8(X)  (((t_data16*)&(X))->low8)
32
33
typedef union
34
{
35
    uint16_t as_uint16;
36
    int16_t  as_int16;
37
    uint8_t  as_uint8[2];       // avr uses little endian - LSB is uint8[0]
38
    int8_t   as_int8[2];
39
    struct                      // if code runs on other endianess - change here
40
      {
41
        uint8_t  low8;
42
        uint8_t  high8;
43
      };
44
    struct                      // if code runs on other endianess - change here
45
      {
46
        uint8_t  nibble0:4;
47
        uint8_t  nibble1:4;
48
        uint8_t  nibble2:4;
49
        uint8_t  nibble3:4;
50
      };
51
} t_data16;
52
53
typedef struct
54
  {
55
    unsigned int volume;        // aktueller Istwert (16 bit, 8.8)
56
    unsigned char target;       // aktueller Zielwert
57
    unsigned char delta;        // aktuelle Schrittweite
58
  } t_lightdimm;
59
60
t_lightdimm lightdimm[NUM_OF_DMX];      // (64)

Der Effekt ist, dass lightdimm[d].target und dmx_value[d] 
unterschiedliche Werte haben, obwohl doch vom gleichen Wert aus 
zugeweisen.

Ich habe dann probehalber mal den Code umformuliert und eine lokale 
Variable verwendet, dann gehts:
1
void dmx_direct_stepup_okay(unsigned char *validmap)
2
  {
3
    unsigned int my_bright; // temp
4
    unsigned char d;  // dmx-channel
5
    unsigned char mask = 1;
6
7
    for (d=0; d<NUM_OF_DMX; d++)
8
      {
9
        if (*validmap & mask)
10
          {
11
            my_bright = lightdimm[d].volume;
12
            if (HIGH8(my_bright) == 255);                             // max
13
            else
14
              {
15
                my_bright += 256; 
16
                lightdimm[d].volume = my_bright;                                   // full step
17
                lightdimm[d].target = HIGH8(my_bright);
18
                dmx_value[d] = HIGH8(my_bright);
19
              }
20
          }
21
        mask <<= 1;
22
        if (!mask)
23
          {
24
            mask = 1;
25
            validmap++;
26
          }
27
      }
28
    flag_set(F_update_grayscale);
29
  }

Und hier noch die Assemblerlistungs:
1
000064b8 <dmx_direct_stepup_okay>:
2
3
4
void dmx_direct_stepup_okay(unsigned char *validmap)
5
  {
6
    64b8:   cf 93          push   r28
7
    64ba:   df 93          push   r29
8
    64bc:   e1 e4          ldi   r30, 0x41   ; 65
9
    64be:   f7 e3          ldi   r31, 0x37   ; 55
10
    64c0:   c3 e4          ldi   r28, 0x43   ; 67
11
    64c2:   d7 e3          ldi   r29, 0x37   ; 55
12
    64c4:   6f e0          ldi   r22, 0x0F   ; 15
13
    64c6:   7e e3          ldi   r23, 0x3E   ; 62
14
    64c8:   41 e0          ldi   r20, 0x01   ; 1    mask = 1;
15
16
    for (d=0; d<NUM_OF_DMX; d++)
17
      {
18
        if (*validmap & mask)
19
    64ca:   dc 01          movw   r26, r24
20
    64cc:   2c 91          ld   r18, X
21
    64ce:   24 23          and   r18, r20
22
    64d0:   51 f0          breq   .+20        ; 0x64e6 <dmx_direct_stepup_okay+0x2e>
23
          {
24
            my_bright = lightdimm[d].volume;
25
    64d2:   20 81          ld   r18, Z
26
    64d4:   31 81          ldd   r19, Z+1   ; r18-r19 = my_bright
27
            if (HIGH8(my_bright) == 255);                             // max
28
    64d6:   3f 3f          cpi   r19, 0xFF   ; 255
29
    64d8:   31 f0          breq   .+12        ; 0x64e6 <dmx_direct_stepup_okay+0x2e>
30
            else
31
              {
32
                my_bright += 256; 
33
    64da:   33 95          inc   r19         ; Highbyte ++
34
                lightdimm[d].volume = my_bright;                                   // full step
35
    64dc:   20 83          st   Z, r18      ; 2* Speichern
36
    64de:   31 83          std   Z+1, r19   ; 0x01
37
                lightdimm[d].target = HIGH8(my_bright);
38
    64e0:   38 83          st   Y, r19
39
                dmx_value[d] = HIGH8(my_bright);
40
    64e2:   db 01          movw   r26, r22
41
    64e4:   3c 93          st   X, r19
42
              }
43
          }
44
        mask <<= 1;
45
    64e6:   44 0f          add   r20, r20
46
        if (!mask)
47
    64e8:   11 f4          brne   .+4         ; 0x64ee <dmx_direct_stepup_okay+0x36>
48
          {
49
            mask = 1;
50
            validmap++;
51
    64ea:   01 96          adiw   r24, 0x01   ; 1
52
              }
53
          }
54
        mask <<= 1;
55
        if (!mask)
56
          {
57
            mask = 1;
58
    64ec:   41 e0          ldi   r20, 0x01   ; 1             Pointerverschieben
59
    64ee:   34 96          adiw   r30, 0x04   ; 4
60
    64f0:   24 96          adiw   r28, 0x04   ; 4
61
    64f2:   6f 5f          subi   r22, 0xFF   ; 255
62
    64f4:   7f 4f          sbci   r23, 0xFF   ; 255
63
  {
64
    unsigned int my_bright; // temp
65
    unsigned char d;  // dmx-channel
66
    unsigned char mask = 1;
67
68
    for (d=0; d<NUM_OF_DMX; d++)
69
    64f6:   b8 e3          ldi   r27, 0x38   ; 56
70
    64f8:   e1 34          cpi   r30, 0x41   ; 65
71
    64fa:   fb 07          cpc   r31, r27
72
    64fc:   31 f7          brne   .-52        ; 0x64ca <dmx_direct_stepup_okay+0x12>
73
    64fe:   08 9a          sbi   0x01, 0   ; 1
74
            mask = 1;
75
            validmap++;
76
          }
77
      }
78
    flag_set(F_update_grayscale);
79
  }
80
    6500:   df 91          pop   r29
81
    6502:   cf 91          pop   r28
82
    6504:   08 95          ret
83
84
;--------------------------------------------------------------------------------------------------
85
86
00006506 <dmx_direct_stepup_fail>:
87
88
void dmx_direct_stepup_fail(unsigned char *validmap)
89
  {
90
    6506:   cf 93          push   r28
91
    6508:   df 93          push   r29
92
    650a:   e1 e4          ldi   r30, 0x41   ; 65
93
    650c:   f7 e3          ldi   r31, 0x37   ; 55
94
    650e:   c3 e4          ldi   r28, 0x43   ; 67
95
    6510:   d7 e3          ldi   r29, 0x37   ; 55
96
    6512:   20 e0          ldi   r18, 0x00   ; 0
97
    6514:   30 e0          ldi   r19, 0x00   ; 0
98
    unsigned char d;  // dmx-channel
99
    unsigned char mask = 1;
100
    6516:   61 e0          ldi   r22, 0x01   ; 1
101
102
    for (d=0; d<NUM_OF_DMX; d++)
103
      {
104
        if (*validmap & mask)
105
    6518:   dc 01          movw   r26, r24
106
    651a:   4c 91          ld   r20, X
107
    651c:   46 23          and   r20, r22
108
    651e:   c9 f0          breq   .+50        ; 0x6552 <dmx_direct_stepup_fail+0x4c>
109
          {
110
            if (HIGH8(lightdimm[d].volume) == 255);                             // max
111
    6520:   d9 01          movw   r26, r18
112
    6522:   aa 0f          add   r26, r26
113
    6524:   bb 1f          adc   r27, r27
114
    6526:   aa 0f          add   r26, r26
115
    6528:   bb 1f          adc   r27, r27
116
    652a:   af 5b          subi   r26, 0xBF   ; 191
117
    652c:   b8 4c          sbci   r27, 0xC8   ; 200
118
    652e:   11 96          adiw   r26, 0x01   ; 1
119
    6530:   7c 91          ld   r23, X
120
    6532:   11 97          sbiw   r26, 0x01   ; 1
121
    6534:   7f 3f          cpi   r23, 0xFF   ; 255
122
    6536:   69 f0          breq   .+26        ; 0x6552 <dmx_direct_stepup_fail+0x4c>
123
            else
124
              {
125
                lightdimm[d].volume += 256; 
126
    6538:   40 81          ld   r20, Z
127
    653a:   51 81          ldd   r21, Z+1   ; 0x01
128
    653c:   53 95          inc   r21
129
    653e:   40 83          st   Z, r20
130
    6540:   51 83          std   Z+1, r21   ; 0x01
131
                lightdimm[d].target = HIGH8(lightdimm[d].volume);
132
    6542:   78 83          st   Y, r23      ; <<<<<<<<<<<<<< FEHLER: Hier wird r23 gespeichert, NICHT inkrementiert
133
134
    6544:   a9 01          movw   r20, r18
135
    6546:   41 5f          subi   r20, 0xF1   ; 241
136
    6548:   51 4c          sbci   r21, 0xC1   ; 193
137
            if (HIGH8(lightdimm[d].volume) == 255);                             // max
138
            else
139
              {
140
                lightdimm[d].volume += 256; 
141
                lightdimm[d].target = HIGH8(lightdimm[d].volume);
142
                dmx_value[d] = HIGH8(lightdimm[d].volume);
143
    654a:   11 96          adiw   r26, 0x01   ; 1
144
    654c:   7c 91          ld   r23, X
145
    654e:   da 01          movw   r26, r20
146
    6550:   7c 93          st   X, r23
147
              }
148
          }
149
        mask <<= 1;
150
    6552:   66 0f          add   r22, r22
151
        if (!mask)
152
    6554:   11 f4          brne   .+4         ; 0x655a <dmx_direct_stepup_fail+0x54>
153
          {
154
            mask = 1;
155
            validmap++;
156
    6556:   01 96          adiw   r24, 0x01   ; 1
157
              }
158
          }
159
        mask <<= 1;
160
        if (!mask)
161
          {
162
            mask = 1;
163
    6558:   61 e0          ldi   r22, 0x01   ; 1
164
    655a:   2f 5f          subi   r18, 0xFF   ; 255
165
    655c:   3f 4f          sbci   r19, 0xFF   ; 255
166
    655e:   34 96          adiw   r30, 0x04   ; 4
167
    6560:   24 96          adiw   r28, 0x04   ; 4
168
169
    6562:   20 34          cpi   r18, 0x40   ; 64
170
    6564:   31 05          cpc   r19, r1
171
    6566:   c1 f6          brne   .-80        ; 0x6518 <dmx_direct_stepup_fail+0x12>
172
    6568:   08 9a          sbi   0x01, 0   ; 1
173
            mask = 1;
174
            validmap++;
175
          }
176
      }
177
    flag_set(F_update_grayscale);
178
  }
179
    656a:   df 91          pop   r29
180
    656c:   cf 91          pop   r28
181
    656e:   08 95          ret
Meiner Meinung nach schiebt der Compiler an der Stelle 6542 beim Befehl 
lightdimm[d].target = HIGH8(lightdimm[d].volume); das nicht 
inkrementierte r23 nach target und nicht wie bei der okay-Version die 
inkrementierte Variable.

mfg Wolfgang

von (prx) A. K. (prx)


Lesenswert?

Wolfgang K. schrieb:
> #define HIGH8(X) (((t_data16*)&(X))->high8)
> #define LOW8(X)  (((t_data16*)&(X))->low8)

Diese Methode ist unzulässig. Stichwort Aliasing.

Der Compiler darf, vereinfacht(*) gesagt, davon ausgehen, dass auf eine 
Variable vom Typ T in indirekter Form nur über Pointer auf diesen Typ T 
zugegriffen wird, nicht aber über Pointer auf andere Typen. Hier ist es 
aber ein union*, der auf Teile vom Objekt X zugreift.

*: Wechsel von signed/unsigned ist erlaubt, ebenso char*.

: Bearbeitet durch User
von Oliver (Gast)


Lesenswert?

A. K. schrieb:
> Diese Methode ist unzulässig. Stichwort Aliasing.

Das ist zwar prinzipiell richtig, beim AVR aber kein Problem.

Oliver

von StM (Gast)


Lesenswert?

Hast du dir mal die beiden Werte genau angeschaut(dezimal und binär). 
Ich hatte mal ein ähnliches Problem, da hat das geholfen.

von (prx) A. K. (prx)


Lesenswert?

Oliver schrieb:
> Das ist zwar prinzipiell richtig, beim AVR aber kein Problem.

Ganz sicher? Oder wars früher bloss keines?

Der gezeigte Code spricht eine andere Sprache. Der passt nämlich genau 
zu der Annahme, dass der Compiler die Zuweisung unmittelbar davor als 
für die kritische Zeile nicht relevant betrachtet. Dank der 
Aliasing-Regeln darf er das.

Ist beim avr-gcc -fno-strict-aliasing implizit aktiv? Kann man ansonsten 
mal ausprobieren.

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


Lesenswert?

Oliver schrieb:
> A. K. schrieb:
>> Diese Methode ist unzulässig. Stichwort Aliasing.
>
> Das ist zwar prinzipiell richtig, beim AVR aber kein Problem.

Was hat Aliasing mit dem Target zu tun? GCC mach Aliasing-Analyse 
unabhängig vom Target.

Ich hab schon Code aus "renommierten" Quellen gesehen, die Kommentare 
enthielten wie "This module must be compilesd with optimization turned 
off because of a GCC bug".

3x darfst du raten wo der Fehler war :-)


Da sollte ne Warnung kommen wie "dereferencing type punned pointer 
breaks strict aliasing rules", denn ein t_data16* ist kein Alias für 
einen (vermutlich) int*, unsigned*, uint16_t* etc.

Um zu beurteilen, ob es sich um einen GCC-Bug handelt, ist zumindest 
folgendes notwendig:

- Optionen, mit dem das Programm übersetzt wurde.  Und "Optionen" meint 
dabei wirklich Optionen, nicht irgdendwelche Makefile-Schnippel.

- Version des Compilers.

- Präprozessierte Quelle

All dies erhält man, indem man zu den Optionen -save-temps -v hinzufügt 
und Ausgabe des Compiles verfügbar macht, d.h. i-Datei und 
Consolenausgabe.

Beispiel für Aliasing:
1
int ali (void)
2
{
3
    int i = 0;
4
    * (float*) &i = 123.;
5
    return i;  /* Compiler darf i = 0 als Ergebnis liefern */
6
}

von Wolfgang K. (opendcc)


Lesenswert?

Hallo,

erst mal danke für die Antworten. Ich denke, ich habe das Problem 
verstanden und gehe jetzt halt über eine Wertzuweisung innerhalb der 
union. Eigentlich müßte das Problem doch massenhaft auftreten, immer 
wenn ich irgendwie die Endianess auflösen muß, brauche ich doch im 
Prinzip dieses Aliasing. Ich habe eine bischen gelesen, man hat wohl 
eine Ausnahme bei Umweg über (char *) reingebaut ...

Übrigens, dieses HIGH8() habe ich vermutlich aus einer Funktionen des 
ASF entlehnt.

Compiler ist avr-gcc (AVR_8_bit_GNU_Toolchain_3.4.4._1229) 4.8.1.

-save-temps kann ich noch nachreichen, ich habe nur schon komplett 
umgebaut.

mfg Wolfgang

von Programmierer (Gast)


Lesenswert?

Wolfgang K. schrieb:
> Eigentlich müßte das Problem doch massenhaft auftreten
Tut es, aber keiner wendet die korrekte Lösung an...
> immer
> wenn ich irgendwie die Endianess auflösen muß, brauche ich doch im
> Prinzip dieses Aliasing.
Nein, brauchst du nicht.
1
uint8_t low = 0xAB; uint8_t high = 0xCD;
2
uint16_t combined = (high << 8) | low;
Setzt einen 16bit-Integer aus low & high byte Zusammen. Perfekt 
definiertes Verhalten, funktioniert garantiert immer, unabhängig davon 
ob die CPU Big oder Little Endian ist. Funktioniert auch in die 
Gegenrichtung. Moderne Compiler optimieren das auch entsprechend.

von Programmierer (Gast)


Lesenswert?

Hier noch die Gegenrichtung als Funktion HIGH8 / LOW8 (Makros sind 
hässlich und braucht man hier nicht):
1
inline uint8_t LOW8 (uint16_t i) {
2
  return i & 0xFF;
3
}
4
inline uint8_t HIGH8 (uint16_t i) {
5
  return (i >> 8) & 0xFF;
6
}

von Oliver (Gast)


Lesenswert?

Wolfgang K. schrieb:
> Hallo,
> Eigentlich müßte das Problem doch massenhaft auftreten, immer
> wenn ich irgendwie die Endianess auflösen muß, brauche ich doch im
> Prinzip dieses Aliasing.

Die klassische Lösung geht über einen direkten Zugriff auf die 
Union-member.

Oliver

von Programmierer (Gast)


Lesenswert?

Oliver schrieb:
> Die klassische Lösung geht über einen direkten Zugriff auf die
> Union-member.
union's sind nicht zum konvertieren von Daten geeignet, denn:
Wenn man auf einen Member der union etwas geschrieben hat, darf man 
nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist 
das Ergebnis undefiniert, und macht irgendetwas (was dem Compiler & 
Prozessor gerade gefällt), wie der OP festgestellt hat.
1
union X {
2
  uint16_t a; uint8_t b [2];
3
};
4
uint8_t test (uint16_t i) {
5
  X x; x.a = i;
6
  return x.b[0]; // FEHLER - Zugriff auf b nicht erlaubt!
7
  return x.a & 0xFF; // Zugriff auf a ist erlaubt.
8
}
Der einzige Zweck von union's ist das Sparen von Speicher.

von Wolfgang K. (opendcc)


Lesenswert?

Hallo,

ja, stimmt. Ist inzwischen so. Nur ich kann mich noch erinnern, dass der 
GCC aus
1
uint16_t combined = (high << 8) | low;
 schon mal Folgendes baute:
1
  LADE 0x00 -> low(combined)
2
  LADE high -> high(combined)
3
  LADE low -> low(temp)
4
  LADE 0x00 -> high(temp)
5
  OR low(temp) -> low(combined)
6
  OR high(temp) -> high(combined)
Aber wie gesagt, da hat es riesige Fortschritte gegeben und manch alter 
Code muß angepaßt werden.

von Programmierer (Gast)


Lesenswert?

Wolfgang K. schrieb:
> ja, stimmt. Ist inzwischen so.
Das war schon immer so. Dass die Compiler optimierungstechnisch so 
schlau sind, dass das zum Problem wird, ist noch nicht ganz so lange so.

Wolfgang K. schrieb:
> Nur ich kann mich noch erinnern, dass der GCC
schnell vergessen :o)

von Mark B. (markbrandis)


Lesenswert?

Wolfgang K. schrieb:
> Aber wie gesagt, da hat es riesige Fortschritte gegeben und manch alter
> Code muß angepaßt werden.

Wobei ich mich frage, wieviel alter Code "optimiert" war obwohl die 
Rechenzeit nie auch nur ansatzweise knapp zu werden drohte... ;-)

von Oliver (Gast)


Lesenswert?

Programmierer schrieb:
> Wenn man auf einen Member der union etwas geschrieben hat, darf man
> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist
> das Ergebnis undefiniert,

Ist es nicht. Es ist "implementation defined".

Oliver

von (prx) A. K. (prx)


Lesenswert?

Wolfgang K. schrieb:
> Übrigens, dieses HIGH8() habe ich vermutlich aus einer Funktionen des
> ASF entlehnt.

Ähnlichen Code kannst du auch in frühem Quellcode des Unix-Kernels 
finden. Aber das macht es heute nicht richtiger. Es gab Zeiten, da war 
das ok. Später kamen Zeiten, da war es schon nicht mehr ok, 
funktionierte aber noch. Und mittlerweile leben wir in Zeiten, in den 
der Compiler dir völlig zurecht an Stellen eine Nase dreht, die ewig 
funktioniert haben. GCC hat in den letzten Versionen schon manche Leute 
bös überrascht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

A. K. schrieb:
> GCC hat in den letzten Versionen schon manche Leute
> bös überrascht.

Ich würd es so formulieren: Seit manche Leute neuere Versionen von GCC 
verwenden werden sie von ihren eigenen Programmen bös überracht.  Bzw. 
haben anderen ein Ei ins Nest gelegt mit ihrem Code.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

Yep, so ist es besser formuliert.

von (prx) A. K. (prx)


Lesenswert?

Das ist übrigens ein Grund, weshalb man zusammen mit einem Projekt die 
dabei verwendete Entwicklungsumgebung einfrieren sollte. Damit man 5 
Jahre drauf noch was fixen kann, ohne als Folge zwischenzeitlicher 
Compiler-Updates über solche Spässe zu stolpern. Virtuelle Maschinen 
sind da ungemein praktisch.

von Jim C (Gast)


Lesenswert?

Programmierer schrieb:
> union's sind nicht zum konvertieren von Daten geeignet

Wie würdest du eine uint32_t in vier Bytes zerlegen? Ich hab dafür immer 
eine Union genommen und nie Probleme gehabt, gibt es eine bessere 
Methode?

von Peter II (Gast)


Lesenswert?

A. K. schrieb:
> Virtuelle Maschinen
> sind da ungemein praktisch.

für einen GCC aber etwas übertrieben. Bei mir gibt es in jede Projekt 
ein Batchfile, das setzt einfach die passenden Umgebungsvariable zum 
GCC.

schon kann ich mehrere Projekte mit unterschiedlichen GCC verwenden.

von Max H. (hartl192)


Lesenswert?

Jim C schrieb:
> Wie würdest du eine uint32_t in vier Bytes zerlegen?
Ich würde es so machen
1
uint32_t zahl;
2
uint8_t bytes[4]
3
4
bytes[0] = zahl & 0xFF;
5
zahl = zahl >> 8;
6
bytes[1] = zahl & 0xFF;
7
zahl = zahl >> 8;
8
bytes[2] = zahl & 0xFF;
9
zahl = zahl >> 8;
10
bytes[3] = zahl & 0xFF;

von (prx) A. K. (prx)


Lesenswert?

Jim C schrieb:
> Wie würdest du eine uint32_t in vier Bytes zerlegen?

Genauso wie bei 2 Bytes. Nur sind es dann eben 4 Komponenten.

Alternativ kannst du auch char* oder memcpy verwenden. Die sind 
hochoffiziell zulässig. Auf PC-CPUs aber u.U. etwas schnarchig.

von Peter II (Gast)


Lesenswert?

A. K. schrieb:
> Alternativ kannst du auch char* oder memcpy verwenden. Die sind
> hochoffiziell zulässig. Auf PC-CPUs aber u.U. etwas schnarchig.

wobei dann aber bei verschieden Architekturen was anderes rauskommt.

von (prx) A. K. (prx)


Lesenswert?

Peter II schrieb:
> wobei dann aber bei verschieden Architekturen was anderes rauskommt.

Byteorder. Klar. Aber das ist bei obiger Union auch nicht anders.

von Programmierer (Gast)


Lesenswert?

A. K. schrieb:
> Byteorder. Klar. Aber das ist bei obiger Union auch nicht anders.
Bei den Bitshifts aber schon, da kann man von einer festen bekannten 
Byte-Order in die Reihenfolge der CPU konvertieren.

GCC ist in der Lage, memcpy durch einzelne Instruktionen zu ersetzen 
wenn es passt.

memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf 
die Daten, und einen solchen kann man nur erhalten wenn diese im RAM 
sind, aber nicht wenn sie in Registern sind. So zwingt man den Compiler, 
die Daten in den RAM zu packen, auch wenn sie zuvor in Registern waren. 
Bei Bitshifts kann der Compiler die Berechnung komplett in Registern 
abhandeln.

von (prx) A. K. (prx)


Lesenswert?

Programmierer schrieb:
> memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf
> die Daten, und einen solchen kann man nur erhalten wenn diese im RAM
> sind, aber nicht wenn sie in Registern sind.

Es stimmt zwar, dass man formal eine Adresse braucht. Trotzdem kann es 
sein, dass der entstehende Code dann doch völlig ohne RAM und ohne 
Adresse auskommt. Compiler sind manchmal so schlau.

Aber ich hatte die memcpy und char* Varianten nicht als ultima ratio 
beschrieben, sondern als eine von mehreren Möglichkeiten, nicht ohne 
unangenehme Nebeneffekte. Bei Integer-Typen ist die arithmetische Lösung 
oft vorzuziehen.

von Rolf M. (rmagnus)


Lesenswert?

Oliver schrieb:
> Programmierer schrieb:
>> Wenn man auf einen Member der union etwas geschrieben hat, darf man
>> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist
>> das Ergebnis undefiniert,
>
> Ist es nicht. Es ist "implementation defined".

Hast du einen Beleg für diese Behauptung?

Programmierer schrieb:
> GCC ist in der Lage, memcpy durch einzelne Instruktionen zu ersetzen
> wenn es passt.
>
> memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf
> die Daten, und einen solchen kann man nur erhalten wenn diese im RAM
> sind, aber nicht wenn sie in Registern sind. So zwingt man den Compiler,
> die Daten in den RAM zu packen, auch wenn sie zuvor in Registern waren.

Man zwingt den Compiler zu gar nix. Warum sollte er extra die Daten in 
den RAM kopieren, nur um sie von dort wieder zurück in Register zu 
kopieren? Gerade um sowas zu vermeiden, gibt's einen Optimizer. Zeiger 
können wie andere Variablen auch wegoptimiert werden, wenn sie 
eigentlich nicht notwendig sind.

von Peter II (Gast)


Lesenswert?

Rolf Magnus schrieb:
>> memcpy, unions und casts haben ein Problem: Sie benötigen Pointer auf
>> die Daten, und einen solchen kann man nur erhalten wenn diese im RAM
>> sind, aber nicht wenn sie in Registern sind. So zwingt man den Compiler,
>> die Daten in den RAM zu packen, auch wenn sie zuvor in Registern waren.
>
> Man zwingt den Compiler zu gar nix. Warum sollte er extra die Daten in
> den RAM kopieren, nur um sie von dort wieder zurück in Register zu
> kopieren?

weil oft memcpy eine Blackbox für den Compiler ist, dieser braucht als 
Parameter ein Zeiger und diese kann nicht auf Register zeigen. Damit 
zwingt man den Compiler.

Falls der Compiler memcpy kennt, dann könnte er etwas anders rangehen, 
aber das ist auch etwas Problematisch falls man ein eigenes memcpy hat 
was sicher anders verhält.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter II schrieb:
> Rolf Magnus schrieb:
>> Man zwingt den Compiler zu gar nix. Warum sollte er extra die Daten in
>> den RAM kopieren, nur um sie von dort wieder zurück in Register zu
>> kopieren?
>
> weil oft memcpy eine Blackbox für den Compiler ist, dieser braucht als
> Parameter ein Zeiger und diese kann nicht auf Register zeigen. Damit
> zwingt man den Compiler.

Naja, soooo schwarz ist die Kiste nun auch wieder nicht.  Folgenden Code 
in den Ring geworfen:
 
1
#include <string.h>
2
#include <math.h>
3
4
long blabla (long x, char what)
5
{
6
    long ret = 0xcafebabe;
7
    float f = 2.0f + sin ((float) ret);
8
    struct { char a, b, c, d; } s = { 1, 2, 3, 10 * f };
9
    
10
    if (what == 0)
11
        memcpy (&ret, &f, 4);
12
    if (what == 2)
13
        memcpy (&ret, &s, 4);
14
    
15
    return ret;
16
}

macht ein gcc für AVR zu:
1
blabla:
2
  tst r20
3
  breq .L3
4
  cpi r20,lo8(2)
5
  brne .L4
6
  ldi r22,lo8(1)
7
  ldi r23,lo8(2)
8
  ldi r24,lo8(3)
9
  ldi r25,lo8(11)
10
  ret
11
.L3:
12
  ldi r22,lo8(69)
13
  ldi r23,lo8(99)
14
  ldi r24,lo8(-110)
15
  ldi r25,lo8(63)
16
  ret
17
.L4:
18
  ldi r22,lo8(-66)
19
  ldi r23,lo8(-70)
20
  ldi r24,lo8(-2)
21
  ldi r25,lo8(-54)
22
  ret

Beim Zwingen muss man also schon etwas zwingender sein, etwa mit 
-ffreestanding, -fno-builtin bzw. -fno-builtin-memcpy + 
-fno-builtin-sin, volatile o.ä. oder Optimierung ausknipsen.

von Programmierer (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Oliver schrieb:
>> Programmierer schrieb:
>>> Wenn man auf einen Member der union etwas geschrieben hat, darf man
>>> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist
>>> das Ergebnis undefiniert,
>>
>> Ist es nicht. Es ist "implementation defined".
>
> Hast du einen Beleg für diese Behauptung?
C-Standard, 6.2.6.1.7:
> When a value is stored in a member of an object of union type, the bytes
> of the object representation that do not correspond to that member but do
> correspond to other members take unspecified values.
Okay, es ist nicht verboten die anderen Member auszulesen, aber das 
Ergebnis ist unspecified, d.h. man weiß nicht was herauskommt. Ein 
bestimmter Compiler für eine bestimmte Plattform könnte eine Garantie 
abgeben dass immer etwas verlässliches herauskommt, aber soweit ich weiß 
- und wie man sieht! - tut GCC das nicht. Um Code also wohldefiniert und 
portabel zu halten, sollte man in einem union nicht auf die Werte 
zugreifen, die man nicht zuvor zuletzt geschrieben hat. Alternativen 
wurden oben aufgezeigt.

von Oliver (Gast)


Lesenswert?

Programmierer schrieb:
> C-Standard, 6.2.6.1.7:
>> When a value is stored in a member of an object of union type, the bytes
>> of the object representation that do not correspond to that member but do
>> correspond to other members take unspecified values

Jetzt hat google doch schon einen ganz brauchbaren Überaetzter, warum 
nutzt du den nicht? Der Text beschreibt was anderes.

> Okay, es ist nicht verboten die anderen Member auszulesen, aber das
> Ergebnis ist unspecified, d.h. man weiß nicht was herauskommt. Ein
> bestimmter Compiler für eine bestimmte Plattform könnte eine Garantie
> abgeben dass immer etwas verlässliches herauskommt,

Könnte er.

Oliver

von Programmierer (Gast)


Lesenswert?

Oliver schrieb:
> Jetzt hat google doch schon einen ganz brauchbaren Überaetzter, warum
> nutzt du den nicht? Der Text beschreibt was anderes.
Weil manche Leute den nicht brauchen. Ich habe mir die Freiheit 
genommen, aus der Vorschrift aus dem Standard eine Folgerung zu ziehen. 
Wenn das zu viel auf einmal für dich war, entschuldige ich mich.

Oliver schrieb:
> Könnte er.
Macht der GCC aber nicht.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Programmierer schrieb:
> Rolf Magnus schrieb:
>> Oliver schrieb:
>>> Programmierer schrieb:
>>>> Wenn man auf einen Member der union etwas geschrieben hat, darf man
>>>> nur diesen Member wieder lesen, aber nicht die anderen! Sonst ist
>>>> das Ergebnis undefiniert,
>>>
>>> Ist es nicht. Es ist "implementation defined".
>>
>> Hast du einen Beleg für diese Behauptung?
> C-Standard, 6.2.6.1.7:
>> When a value is stored in a member of an object of union type, the bytes
>> of the object representation that do not correspond to that member
>> but do correspond to other members take unspecified values.
> Okay, es ist nicht verboten die anderen Member auszulesen, aber das
> Ergebnis ist unspecified, d.h. man weiß nicht was herauskommt. Ein

Ein paar Ausnahmen gibt's aber schon: Eine davon: Falls die erste 
Komponente aller Union-Members den gleichen (WIMRE skalaren) Typ hat. 
Damit kann man dann den Inhalt der Union identifizieren:
1
union
2
{
3
    int id;
4
    struct { int id; data1_t d1; data2_t d2; ... } s1;
5
    struct { int id; data3_t d3; data4_t d4; ... } s2;
6
    ...
7
} ...

> bestimmter Compiler für eine bestimmte Plattform könnte eine Garantie
> abgeben dass immer etwas verlässliches herauskommt, aber soweit ich weiß
> - und wie man sieht! - tut GCC das nicht.

Doch, gerade GCC tut es :-)  Und das wird auch im Kleingedruckten 
zugesichert:

"Most frequently reported non-Bugs"

https://gcc.gnu.org/bugs/#nonbugs

>> [...] To fix the code above, you can use a union instead of a cast
>> (note that this is a GCC extension which might not work with
>> other compilers) [...]

Der kleine aber feine Unterschied ist hier, ob über einen Zeiger 
zugegriffen ("dereferenziert") wird oder nicht, d.h. es geht hier um 
Aliasing und nicht um den Zugriff eines Union-Members über einen 
anderen..

Und im Großgedruckten lautet das etwas formaler:

"C Implementation-defined behavior: Structures, unions, enumerations, 
and bit-fields"

http://gcc.gnu.org/onlinedocs/gcc/Structures-unions-enumerations-and-bit-fields-implementation.html

>> * A member of a union object is accessed using a member of
>> a different type (C90 6.3.2.3).
>>
>> The relevant bytes of the representation of the object are
>> treated as an object of the type used for the access.
>> See Type-punning. This may be a trap representation.

> Um Code also wohldefiniert und portabel zu halten,
> sollte man in einem union nicht auf die Werte zugreifen,
> die man nicht zuvor zuletzt geschrieben hat.
> Alternativen wurden oben aufgezeigt.

Es ist eben genug Code in der Welt, der sich nicht daran hält.  Siehe 
Atmel.  Dieses LOW8 / HIGH8 Hacks wären nen Bug-Report wert — an Atmel 
wohlgemerkt, nicht an GCC.

von (prx) A. K. (prx)


Lesenswert?

Peter II schrieb:
> für einen GCC aber etwas übertrieben.

Yep. Es gibt aber nicht nur GCC. Und es gibt GCCs, die eng in 
kommerzielle IDEs eingebettet sind - bei ARM recht beliebt.

Wobei ich Entwicklungsumgebungen schon vorneweg gerne in VMs stecke, 
weil sie dann Host-Migrationen völlig unbeschadet überstehen.

Es passt zwar aufgrund der Linux-Basis nicht perfekt hier rein, aber als 
Beispiel taugt AVMs Router-Problem vor ein paar Monaten trotzdem ein 
wenig. Die haben innerhalb einiger Tage fast die gesamte Produktpalette 
aus einem Jahrzehnt gefixt, darunter Geräte, die schon viele viele Jahre 
aus der Wartung waren. Um nicht in ähnliche Fallen zu laufen müssen die 
eine entsprechende Infrastruktur alter Umgebungen recht konsequent 
gepflegt haben - meinen Respekt dafür haben sie.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Programmierer schrieb:
> Ich habe mir die Freiheit
> genommen, aus der Vorschrift aus dem Standard eine Folgerung zu ziehen.

und die ist falsch.

"When a value is stored in a member of an object of union type, the 
bytes
of the object representation that do not correspond to that member but 
do
correspond to other members take unspecified values."

ist übersetzt:

Wenn ein Wert in einem Mitglied eines Objektes vom Typ Union gespeichert 
wird, nehmen die Bytes der Objektdarstellung, die nicht zu diesem 
Mitglied gehören, undefinierte Werte an.

Der Gag des Typecasts durch eine Union ist aber gerade, daß über die 
beteiligten Member auf die selben Bytes zugriffen wird.
Das "undefiniert" in dem zitierten Satz bezieht sich auf die Bytes, die 
gar nicht beschrieben werden (weil z.B die Union größer ist als der 
member, der zuletzt geschrieben wurde).

Daher trifft der ganze Satz hier nicht zu.

Oliver

: Bearbeitet durch User
von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Man muss ja nicht unbedingt eine Union nehmen, um Endianess zu prüfen. 
Wenn man schon nicht über Standards wie htonl etc kommunizieren will, 
gehts auch mit einem char * Pointer, der auf ein long oder short zeigt.

Casts in C erlauben eigentlich genau die Typenverwandlungen, welche 
die Leute komischerweise immer mit Unions erledigen wollen. Dafür sind 
Unions aber gar nicht gedacht.

von Oliver S. (oliverso)


Lesenswert?

Frank M. schrieb:
> Casts in C erlauben eigentlich genau die Typenverwandlungen, welche
> die Leute komischerweise immer mit Unions erledigen wollen.

Hm. Wie genau zerlegt man den rein über casts ein 32-Bit float in seine 
4 Bytes?

Oliver

von Mark B. (markbrandis)


Lesenswert?

Oliver S. schrieb:
> Hm. Wie genau zerlegt man den rein über casts

Hat niemand behauptet, dass es ausschließlich nur durch Casting gemacht 
werden soll. ;-)

Hier ein Beispiel:

http://stackoverflow.com/questions/3991478/building-a-32bit-float-out-of-its-4-composite-bytes-c

von Sebastian V. (sebi_s)


Lesenswert?

Oliver S. schrieb:
> Hm. Wie genau zerlegt man den rein über casts ein 32-Bit float in seine
> 4 Bytes?

Das oben genannte strict aliasing hat einige Ausnahmen. So ist es z.B. 
erlaubt beliebige Pointer zu einem char* zu casten und so auf die 
einzelnen Bytes zuzugreifen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Oliver S. schrieb:

> Der Gag des Typecasts durch eine Union ist aber gerade, daß über die
> beteiligten Member auf die selben Bytes zugriffen wird.

Das ist *KEIN* Cast! Und die zitierte Stelle hat auch nur entfernt mit 
Type-Punning zu tun.

Und das Verhalten ist abhängig von der C-Version.

Bei älteren steht dazu:

> With one exception, if the value of a member of a union object
> is used when the most recent store to the object was to a
> different member, the behavior is implementation-defined.

Dann wird die o.g. Ausnahme besprochen:

> One special guarantee is made in order to simplify the use
> of unions: If a union contains several structures that share
> a common initial sequence, and if the union object currently
> contains one of these structures, it is permitted to inspect
> the common initial part of any of them anywhere that a
> declaration of the completed type of the union is visible.
> Two structures share a common initial sequence if corresponding
> members have compatible types and, for bit-fields, the same
> widths for a sequence of one or more initial members.

In neueren Versionen wird Type-Punning explizit erlaubt:

> If the member used to access the contents of a union object
> is not the same as the member last used to store a value in
> the object, the appropriate part of the object representation
> of the value is reinterpreted as an object representation in
> the new type [...] a process sometimes called "type punning".

Nochmals:  Der OP, bzw. den Code den er verwendet, hat ein Problem mit 
Aliasing, nicht mit Type-Punning.

von Oliver S. (oliverso)


Lesenswert?

Johann L. schrieb:
> Oliver S. schrieb:
>
>> Der Gag des Typecasts durch eine Union ist aber gerade, daß über die
>> beteiligten Member auf die selben Bytes zugriffen wird.
>
> Das ist *KEIN* Cast!

ok, selbstvertändlich nicht.

> Nochmals:  Der OP, bzw. den Code den er verwendet, hat ein Problem mit
> Aliasing, nicht mit Type-Punning.

Ist so. Das bezog sich auf die Diskussion mit Programmierer du die 
direkte union-Variante.

Oliver

von Eric (Gast)


Lesenswert?

Oliver S. schrieb:

> Hm. Wie genau zerlegt man den rein über casts ein 32-Bit float in seine
> 4 Bytes?
1
float32 flot;
2
char * cast = (char *) &flot;
3
char das_2e_byte = cast[1];

Beitrag #7414944 wurde von einem Moderator gelöscht.
Beitrag #7416085 wurde von einem Moderator gelöscht.
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.