Forum: Compiler & IDEs avr-gcc 4.9, Atmel Studio 7 unterschlägt Code


von Adib (Gast)


Lesenswert?

Hallo Leute,

ich bin fast verzweifelt. Der Code unten läuft korrekt unter Studio 6 
mit avr-gcc 4.8 aber mit dem neuen avr-gcc ab (einschliesslich) 
Optimierung -O2 wird der Test der Adresse einfach "wegoptimiert".
Liegt das jetzt an meinem Code oder ist das ein Compiler-Bug?

Danke für eure Hilfe.

Der Code ist aus meinem Projekt.
Es werden 2 Messages dekodiert. Dabei steht am Index 5 und 6 eine 16bit 
ID.
Der Dekoder soll die Messages bearbeiten, wenn:
- diese ID gleich der globalen fkt_device_id ist,
- oder einer Broadcast Maske entspricht: die oberen 3 Byte 0xeee0 und im 
niederwertigsten Nibble das bit1 (0x02) gesetzt ist
Bei -O0, -O1 ist noch alles ok. Bei -O2 wird aber der Block beginend 
mit:
  if(((addressee & FKT_DEVICE_ID_BROADCAST_bm) ==
komplett weggelassen.
Ich habe keien Ahnung, was der Compiler da sieht, warum er das weglassen 
sollte. Die Message2 sollte bei diesem if() matchen.

Grüße, Adib.
--
1
#include <avr/io.h>
2
#include <stdbool.h>
3
#include <stdlib.h>
4
5
typedef uint8_t fkt_size_t;
6
7
#define FKT_DEVICE_ID_BROADCAST_bm (0xFFF0)
8
#define FKT_DEVICE_ID_BROADCAST_MASK_bm (0xEEE0)
9
#define FKT_DEVICE_ID_BROADCAST_TYPE_NODE (0x0002)
10
11
#define FKT_SYSTEM_VERSIONINFO  0x41
12
13
static uint16_t fkt_device_id;
14
15
uint8_t fkt_decoder_version_info(uint8_t payload, uint8_t *data, fkt_size_t len)
16
{
17
  fkt_device_id += payload;
18
  return 0;
19
}
20
21
uint8_t fkt_decoder(uint8_t payload, uint8_t *data, fkt_size_t len)
22
{
23
  uint8_t result = 0;
24
  uint8_t system = data[1];
25
  uint16_t addressee = (data[5] * 0x100) + data[6];
26
  bool isforme = false;
27
  
28
  if(addressee == fkt_device_id) {
29
    isforme = true;
30
  }
31
  
32
  if(((addressee & FKT_DEVICE_ID_BROADCAST_bm) == FKT_DEVICE_ID_BROADCAST_MASK_bm)  && ((addressee & FKT_DEVICE_ID_BROADCAST_TYPE_NODE) != 0)) {
33
    isforme = true;
34
  }
35
  
36
  if(isforme == true)
37
  {
38
    if(system == FKT_SYSTEM_VERSIONINFO)
39
    {
40
      result = fkt_decoder_version_info(payload, data, len);
41
    }
42
  }
43
  return result;
44
}
45
46
uint8_t message[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 , 0x09 };
47
uint8_t message2[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0xee, 0xe2, 0x07, 0x08 , 0x09 };
48
49
int main(void)
50
{
51
  fkt_device_id = 0x23;
52
  
53
  fkt_decoder(1, message, sizeof(message));
54
  fkt_decoder(2, message2, sizeof(message2));
55
  
56
    /* Replace with your application code */
57
    while (1) 
58
    {
59
    
60
  }
61
}

von Peter II (Gast)


Lesenswert?

gibt es Warnungen?

Bin mir nicht sicher ob man die Kostanden lieber als unsigned definieren 
sollte.
1
#define FKT_DEVICE_ID_BROADCAST_bm (0xFFF0u)
2
#define FKT_DEVICE_ID_BROADCAST_MASK_bm (0xEEE0u)
3
#define FKT_DEVICE_ID_BROADCAST_TYPE_NODE (0x0002u)

von Felix A. (madifaxle)


Lesenswert?

Wenn die Optimierung das entfernt, kann es sein, dass der Compiler hier 
glaubt, dass sich am Ergebnis nie was ändern wird. Du könntest das 
versuchen:

volatile bool isforme = false;

Der Compiler wird so angewiesen, immer "isforme" zu beschreiben, da sich 
diese Variable ändern kann.

Gut wäre es, zusätzlich dazu hier im Forum nach "volatile" zu suchen. Da 
gibt es viele Themen, die das Verhalten und den Einfluss beleuchten. Ich 
bin darin nicht sonderlich firm.

: Bearbeitet durch User
von Adib (Gast)


Lesenswert?

Hallo Peter,

Danke für die Antwort aber das bringt noch keinerlei Änderungen.

übrigens die Kommandozeile lautet:
1
"C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin\avr-gcc.exe" -o gcctest01.elf  main.o   -Wl,-Map="gcctest01.map" -Wl,--start-group -Wl,-lm  -Wl,--end-group -Wl,--gc-sections -mrelax -mmcu=atmega128 -B "C:\Program Files (x86)\Atmel\Studio\7.0\Packs\atmel\ATmega_DFP\1.0.90\gcc\dev\atmega128"

Adib.

von Peter II (Gast)


Lesenswert?

Felix A. schrieb:
> Wenn die Optimierung das entfernt, kann es sein, dass der Compiler hier
> glaubt, dass sich am Ergebnis nie was ändern wird. Du könntest das
> versuchen:

er hat damit ja sogar recht. die Funktion kann nur 0 als Ausgang 
liefern.

Und damit macht das return gar keine sinn.

von Peter II (Gast)


Lesenswert?

Adib schrieb:
> übrigens die Kommandozeile lautet:

wenn ich das richtig sehe, sind dort keine Warnungen eingeschaltet. 
Setze mal noch den Parameter -Wall

von Wolfgang (Gast)


Lesenswert?

Felix A. schrieb:
> Wenn die Optimierung das entfernt, kann es sein, dass der Compiler hier
> glaubt, dass sich am Ergebnis nie was ändern wird.

Der Compiler hat nichts zu glauben.

Er soll den Code entsprechend den festgelegten Sprachregeln von C in 
Binärcode umsetzen. Wenn der Quellcode nicht diesen Regeln entspricht, 
soll er eine Fehlermeldung von sich geben. Und wenn er - warum auch 
immer - den Verdacht hat, dass da irgendetwas komisch ist und vielleicht 
so nicht der Intention des Autors entspricht, soll er eine Warnung 
ausspucken.

von Carl D. (jcw2)


Lesenswert?

Wenn man mal ganz genau hinschaut, dann macht dieser Code außer in 
"while(1)" Kreise zu drehen, exakt nichts. Es werden kompliziert nie 
wieder benutzte Variablen verändert. Das erkennt der GCC in der 4.9 
Version offenbar, denn mit 4.9 gab es enrme Verbesserungen beim 
Optimizer, und macht was seine Freiheit ist: weg damit!

von Adib (Gast)


Lesenswert?

Hallo Felix,

habe den Code mal so geändert:
1
#define FKT_DEVICE_ID_BROADCAST_bm (0xFFF0u)
2
#define FKT_DEVICE_ID_BROADCAST_MASK_bm (0xEEE0u)
3
#define FKT_DEVICE_ID_BROADCAST_TYPE_NODE (0x0002u)
4
5
static uint16_t fkt_device_id;
6
static volatile int foo = 0;
7
8
void fkt_decoder(uint8_t payload, uint8_t *data, fkt_size_t len)
9
{
10
  uint16_t addressee = (data[5] * 0x100) + data[6];
11
  bool isforme = false;
12
  
13
  if(addressee == fkt_device_id) {
14
    isforme = true;
15
  }
16
  
17
  if(((addressee & FKT_DEVICE_ID_BROADCAST_bm) == FKT_DEVICE_ID_BROADCAST_MASK_bm)  && ((addressee & FKT_DEVICE_ID_BROADCAST_TYPE_NODE) != 0)) {
18
    isforme = true;
19
  }
20
  
21
  if(isforme == true)
22
  {
23
    foo++;
24
  }
25
}

bei -O1 macht er foo++ und bei -O2 nicht.

Habs im Simulator mit einem Brechpunkt getestet.

Mit eingeschalteten Warnings -wall, -wpedantic kommen nur Warningen, 
dass die Parameter payload und len nicht benutzt werden.

Adib.
--

von Felix A. (madifaxle)


Lesenswert?

Wurde das hier
1
if(addressee == fkt_device_id) {
2
    isforme = true;
3
  }
4
  
5
  if(((addressee & FKT_DEVICE_ID_BROADCAST_bm) == FKT_DEVICE_ID_BROADCAST_MASK_bm)  && ((addressee & FKT_DEVICE_ID_BROADCAST_TYPE_NODE) != 0)) {
6
    isforme = true;
7
  }

denn wegoptimiert? Dann macht er das foo++ natürlich nicht.

von Wolfgang (Gast)


Lesenswert?

Adib schrieb:
> bei -O1 macht er foo++ und bei -O2 nicht.

Ist doch ok. Wenn der ganze Zirkus nur dazu dient, darüber zu 
entscheiden, ob die nicht benutzte Variable foo++ geändert werden soll, 
ist das sein gutes Recht.

von Peter II (Gast)


Lesenswert?

Wolfgang schrieb:
> Ist doch ok. Wenn der ganze Zirkus nur dazu dient, darüber zu
> entscheiden, ob die nicht benutzte Variable foo++ geändert werden soll,
> ist das sein gutes Recht.

nein ist es nicht,  foo  ist volatile ist darf nicht wegoptimiert 
werden.

von Carl D. (jcw2)


Lesenswert?

> bei -O1 macht er foo++ und bei -O2 nicht.

Er kann die fkt_decoder() auch einfach zur Compile-Zeit ausführen. 
Payload gibt es vermutlich gar nicht, da die nie gelesen werden und foo, 
weil volatile, existiert, wird am Anfang von main() inkrementiert.
Ich finde zwar Assembler schreiben für (fast immer) überflüssig, aber 
Assembler lesen (können) macht bei solchen Fragen schlau. (.lss-File)

: Bearbeitet durch User
von Adib (Gast)


Lesenswert?

Der Unterschied zwischen -O1 und -O2 ist der Test auf die Maske.
Der Test auf die device_id wird immer korrekt ausgeführt.


Bei -O2 sieht der generierte Code so aus:

1
void fkt_decoder(uint8_t payload, uint8_t *data, fkt_size_t len)
2
{
3
  uint16_t addressee = (data[5] * 0x100) + data[6];
4
  c8:  fb 01         movw  r30, r22
5
  ca:  85 81         ldd  r24, Z+5  ; 0x05
6
  cc:  90 e0         ldi  r25, 0x00  ; 0
7
  ce:  98 2f         mov  r25, r24
8
  d0:  88 27         eor  r24, r24
9
  d2:  26 81         ldd  r18, Z+6  ; 0x06
10
  d4:  82 0f         add  r24, r18
11
  d6:  91 1d         adc  r25, r1
12
  bool isforme = false;
13
  
14
  if(addressee == fkt_device_id) {
15
  d8:  20 91 16 01   lds  r18, 0x0116
16
  dc:  30 91 17 01   lds  r19, 0x0117
17
  e0:  82 17         cp  r24, r18
18
  e2:  93 07         cpc  r25, r19
19
  e4:  09 f0         breq  .+2        ; 0xe8 <fkt_decoder+0x20>
20
  e6:  08 95         ret
21
    isforme = true;
22
  }
23
  
24
  if(isforme == true)
25
  {
26
    foo++;
27
  e8:  80 91 14 01   lds  r24, 0x0114
28
  ec:  90 91 15 01   lds  r25, 0x0115
29
  f0:  01 96         adiw  r24, 0x01  ; 1
30
  f2:  90 93 15 01   sts  0x0115, r25
31
  f6:  80 93 14 01   sts  0x0114, r24
32
  fa:  08 95         ret

Bei -O1 sieht das so aus:
1
void fkt_decoder(uint8_t payload, uint8_t *data, fkt_size_t len)
2
{
3
  uint16_t addressee = (data[5] * 0x100) + data[6];
4
  c8:  fb 01         movw  r30, r22
5
  ca:  85 81         ldd  r24, Z+5  ; 0x05
6
  cc:  90 e0         ldi  r25, 0x00  ; 0
7
  ce:  98 2f         mov  r25, r24
8
  d0:  88 27         eor  r24, r24
9
  d2:  26 81         ldd  r18, Z+6  ; 0x06
10
  d4:  82 0f         add  r24, r18
11
  d6:  91 1d         adc  r25, r1
12
  bool isforme = false;
13
  
14
  if(addressee == fkt_device_id) {
15
  d8:  40 91 16 01   lds  r20, 0x0116
16
  dc:  50 91 17 01   lds  r21, 0x0117
17
    isforme = true;
18
  }
19
  
20
  if(((addressee & FKT_DEVICE_ID_BROADCAST_bm) == FKT_DEVICE_ID_BROADCAST_MASK_bm)  && ((addressee & FKT_DEVICE_ID_BROADCAST_TYPE_NODE) != 0)) {
21
  e0:  9c 01         movw  r18, r24
22
  e2:  20 7f         andi  r18, 0xF0  ; 240
23
  e4:  20 3e         cpi  r18, 0xE0  ; 224
24
  e6:  3e 4e         sbci  r19, 0xEE  ; 238
25
  e8:  11 f4         brne  .+4        ; 0xee <fkt_decoder+0x26>
26
  ea:  81 fd         sbrc  r24, 1
27
  ec:  03 c0         rjmp  .+6        ; 0xf4 <fkt_decoder+0x2c>
28
    isforme = true;
29
  }
30
  
31
  if(isforme == true)
32
  ee:  84 17         cp  r24, r20
33
  f0:  95 07         cpc  r25, r21
34
  f2:  49 f4         brne  .+18       ; 0x106 <fkt_decoder+0x3e>
35
  {
36
    foo++;
37
  f4:  80 91 14 01   lds  r24, 0x0114
38
  f8:  90 91 15 01   lds  r25, 0x0115
39
  fc:  01 96         adiw  r24, 0x01  ; 1
40
  fe:  90 93 15 01   sts  0x0115, r25
41
 102:  80 93 14 01   sts  0x0114, r24
42
 106:  08 95         ret

PS: sorry für die langen Code-Postings.
Adib.

von Felix A. (madifaxle)


Lesenswert?

Hast du mal isforme als volatile definiert? Das scheint ja das 
eigentliche Problem zu sein.

von Adib (Gast)


Lesenswert?

Hallo Leute,

der Fehler ist mir in einem realen Projekt aufgefallen, wo die Daten per 
serielle Schnittstelle einkommen und dann geparsed werden.
Zumindest da KANN der Compiler einfach garnicht wissen, welche Message 
geparsed wird.

Ich habe den Code soweit verkleinert, dass sich der Fehler mit -O1 und 
-O2 nachstellen lässt.

Ich kann mir das einfach nicht erklären, was genau abgeht.
Gibt es irgendwelche Optionen, die den Compiler zu veranlassen, 
Zwischenschritte auszugeben?

Danke, Adib.
--

von Adib (Gast)


Lesenswert?

Felix,

auch mit:
1
volatile bool isforme
ist das gleiche Ergebnis.

Adib.
--

von Peter II (Gast)


Lesenswert?

las mal bitte die jeweil den rechten oder linken teil von && weg. Das 
man sieht welches er von beiden annimmt das es nicht war ist.

von Felix A. (madifaxle)


Lesenswert?

Ich kann da nur noch raten. Es könnte neben "isforme" nochmal zusätzlich 
volatile auf "addressee" angewendet werden, da diese Prüfung 
wegoptimiert wird. Aber dann wüsste ich auch nicht mehr.

von Adib (Gast)


Lesenswert?

Hallo Peter,

Diese Code-Sequenz bleibt stehen:
1
  if( ((addressee & (uint16_t)0x0002) != 0)) {
2
    isforme = true;
3
  }

Diese Code-Sequenz wird entfernt:
1
  if((((uint16_t)addressee & (uint16_t)0xfff0) == (uint16_t)0xeee0) ) {
2
    isforme = true;
3
  }

Adib.

von Peter II (Gast)


Lesenswert?

Adib schrieb:
> Diese Code-Sequenz wird entfernt:  if((((uint16_t)addressee &
> (uint16_t)0xfff0) == (uint16_t)0xeee0) ) {
>     isforme = true;
>   }

schreibe mal davor:

addressee = 0xEEE2;

(und ändere mal addressee in Adresse ab)

von Adib (Gast)


Lesenswert?

Bei den beiden Durchläufen steht in addressee wie erwartet 0x0506 und 
0xeee2 drin.

Adib.
--

von Peter II (Gast)


Lesenswert?

Adib schrieb:
> Bei den beiden Durchläufen steht in addressee wie erwartet 0x0506 und
> 0xeee2 drin.

wenn es aber direkt davor steht, kann man das Problem etwas weiter 
eingrenzen.

von Adib (Gast)


Lesenswert?

Peter,

wenn ich "addressee = 0xEEE2;" schreibe willst du den Code nicht sehen 
;-)

er generiert die Funktion decode nicht mehr, sondern incrementiert 2x 
die volatile Variable foo.
Es findet kein Test mehr auf "device_id" noch auf die Maske statt - gut 
wegoptimiert halt.
Ich hatte die Testfälle so gestaltet, dass er testen muss und dann 
wenigstens einmal isforme = true setzt.

Adib.
--

von Peter II (Gast)


Lesenswert?

Adib schrieb:
> wenn ich "addressee = 0xEEE2;" schreibe willst du den Code nicht sehen
> ;-)

das ist doch schon mal gut, damit wissen wir das der vergleich richtig 
ist. Er geht also davon aus das in addressee niemals dieser wert stehen 
kann.

von Adib (Gast)


Lesenswert?

Hallo Peter,

das glaube ich jetzt auch langsam.
Wie bildet man aus einem Byte-Stream korrekt einen 16bit unsignet Wert?

Dieser Code wird komplett wegoptimiert:
1
void fkt_decoder(uint8_t payload, uint8_t *data, fkt_size_t len)
2
{
3
  uint16_t addressee = (data[5] * 0x100) | data[6];
4
  bool isforme = false;
5
6
  if((((uint16_t)addressee & (uint16_t)0xfff0) == (uint16_t)0xeee0) ) {
7
    isforme = true;
8
  }
9
  
10
  if(isforme == true)
11
  {
12
    foo++;
13
  }
14
}

mit eingefügtem Test auf Device_id gibt es zumindest den FunktionsRumpf 
(ohne Test auf die Maske)
1
  if(addressee == fkt_device_id) {
2
    isforme = true;
3
  }

Adib.

von Peter II (Gast)


Lesenswert?

Adib schrieb:
> Wie bildet man aus einem Byte-Stream korrekt einen 16bit unsignet Wert?

dein Schreibweise ist zwar etwas ungewöhnlich, aber so sollt es richtig 
sein.


Üblicher ist
1
uint16_t addressee = (data[5] <<8) | data[6];

von Adib (Gast)


Lesenswert?

Hallo Leute,

ich glaube Peter hat mich auf die richtige Fährte geführt. Es war das 
erzeugen des uint16_t Wertes aus dem uint8_t Byte strom:

so wird der Test auf die Maske nicht wegoptimiert:
1
  uint16_t addressee = ((uint16_t)data[5] << 8) | data[6];
2
  c8:  fb 01         movw  r30, r22
3
  ca:  85 81         ldd  r24, Z+5  ; 0x05
4
  cc:  90 e0         ldi  r25, 0x00  ; 0
5
  ce:  98 2f         mov  r25, r24
6
  d0:  88 27         eor  r24, r24
7
  d2:  26 81         ldd  r18, Z+6  ; 0x06
8
  d4:  82 2b         or  r24, r18

es wird aber der gleiche ASM Code generiert wie bei:
1
  uint16_t addressee = (data[5] * 0x100) | data[6];
Und die Werte in der Variable addressee waren wie erwartet :-O

Gibt es noch eine effizientere Variante der Umwandlung.

Adib.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter II schrieb:
> nein ist es nicht,  foo  ist volatile ist darf nicht wegoptimiert
> werden.

Auch volatiles durfen wegoptimiert werden, z.B. wenn es sich um toten 
Code handelt wie in
1
if (0)
2
   foo++;

von Peter II (Gast)


Lesenswert?

Adib schrieb:
> Gibt es noch eine effizientere Variante der Umwandlung.

nicht wirklich, man könnte ein memcpy schrieben, das ist dann aber nicht 
mehr portabel.

Schade das sich bis jetzt keine der wirklich C Profis gemeldet hat, 
scheint wirklich eine Bug zu sein. Wüsste man warum die Multiplikation 
nicht gehen soll.

Zusammenfassung:
1
uint16_t addressee = ((uint16_t)data[5] << 8) | data[6];
geht
1
uint16_t addressee = (data[5] * 0x100) | data[6];
geht nicht

Richtig?

von Adib (Gast)


Lesenswert?

Fehlerhafter Code:
1
uint8 *data;
2
uint16_t addressee1 = (data[5] * 0x100) | data[6]; // Fehler
3
uint16_t addressee2 = (data[5] << 8) | data[6]; // korrekt
4
uint16_t addressee3 = (data[5] * 0x100U) | data[6]; // korrekt
5
uint16_t addressee4 = ((uint16_t)data[5] * 0x100) | data[6]; // korrekt

Alle Varianten erzeugen bei -O2 die gleichen eigenartige Sequenz - Wert 
in R24/R25:
1
  c8:  fb 01         movw  r30, r22
2
  ca:  85 81         ldd  r24, Z+5  ; 0x05
3
  cc:  90 e0         ldi  r25, 0x00  ; 0
4
  ce:  98 2f         mov  r25, r24
5
  d0:  88 27         eor  r24, r24
6
  d2:  26 81         ldd  r18, Z+6  ; 0x06
7
  d4:  82 2b         or  r24, r18

Adib.
--

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Adib schrieb:
> Gibt es noch eine effizientere Variante der Umwandlung.

Ja, indem avr-gcc eine effizientere Sequenz dafür ausgibt.  Mit 
Rumgefrickel per Union bekommst du den Code vielleicht ein paar Bytes 
kürzer, die korrekte Herangehensweise ist jedoch, dieses 
Effizienzproblem im Compiler selbst zu beheben — was nicht ganz einfach 
ist (wenn's einfach wäre wär's schon längst behoben).

von Adib (Gast)


Lesenswert?

Ich habe noch ein bisschen rumgefrickelt:
1
  uint16_t addressee = *((uint16_t *)(&data[5]));
2
  c8:  fb 01         movw  r30, r22
3
  ca:  85 81         ldd  r24, Z+5  ; 0x05
4
  cc:  96 81         ldd  r25, Z+6  ; 0x06

Das ist Code der also nur unter little Endian geht. Man sollte das evtl 
auf einem Cortex gegenchecken.

Ich denke, das ursprüngliche Problem mit:
1
uint16_t addressee1 = (data[5] * 0x100) | data[6];
ist, dass der Compiler bei dem literal 0x100 den Typ des 
Multiplikationsergebnisses nicht auf int setzt, sondern bei dem Typ der 
Variable bleibt.
Komischerweise setzt er das für seine Optimierung so. Der erzeugte Code 
zeigt ja, dass ein 16bit Ergebnis generiert wird.
Normalerweise sollten ja alle literale als Int behandelt werden :-?
Was mache ich denn da wenn ich eine int8_t Variable mit 0x100 
multiplizieren muss ... ?
Bug or feature?
Also ich tendiere eher zu Bug als zu Feature.

Adib.
--

von Peter II (Gast)


Lesenswert?

Adib schrieb:
> uint16_t addressee1 = (data[5] * 0x100) | data[6]; // Fehler

er geht scheinbar davon aus das
1
data[5] * 0x100

unsigned berechnet wird, damit niemals größer als 32768 werden kann, 
damit kann das höchste bit nicht gesetzte sein, damit ist dann der 
vergleich niemals war.

von Peter II (Gast)


Lesenswert?

Korrektur:

Adib schrieb:
> uint16_t addressee1 = (data[5] * 0x100) | data[6]; // Fehler

er geht scheinbar davon aus das





data[5] * 0x100



signed berechnet wird, damit niemals größer als 32768 werden kann,
damit kann das höchste bit nicht gesetzte sein, damit ist dann der
vergleich niemals war.

von Adib (Gast)


Lesenswert?

Peter II schrieb:
> signed berechnet wird, damit niemals größer als 32768 werden kann,

ok, geh' ich mit. Ist also schlechter Source Code.
Also wenn man:
- unsigned Variablen mit Literalen Multipliziert,
dann
- das Literal auch als Unsigned markieren.

Danke an Alle.

Gute Nacht, Adib.
--

von Peter II (Gast)


Lesenswert?

Adib schrieb:
>> signed berechnet wird, damit niemals größer als 32768 werden kann,
>
> ok, geh' ich mit. Ist also schlechter Source Code.
> Also wenn man:
> - unsigned Variablen mit Literalen Multipliziert,
> dann
> - das Literal auch als Unsigned markieren.

eher so:

0x100 -> wird int

uint8_t * int -> wird zu int * int

damit ist das Ergebnis signed.

von Peter II (Gast)


Lesenswert?

Warum nimmt du eigentlich -O2 und nicht -Os?

Bei -Os würde ich schon erwarten, das er die Umwandlung besser 
optimiert.

von Adib (Gast)


Lesenswert?

Peter II schrieb:
> Warum nimmt du eigentlich -O2 und nicht -Os?
>
> Bei -Os würde ich schon erwarten, das er die Umwandlung besser
> optimiert.

Mich interessiert eigentlich nur die Ausführungsgeschwindigkeit. Flash 
habe ich genug.

Die Konvertierung ist wie bei den anderen -O Varianten. Das erkennt der 
Compiler noch nicht, was ich will.
Einzig das Cast generiert den "optimalen" Code. Ist aber leider nicht 
portabel ....

Adib.
--

von Markus F. (mfro)


Lesenswert?

Aus dem C99 Standard:

If the type of the operand with signed integer type can represent all of 
the values of the type of the operand with unsigned integer type, the 
operand with unsigned integer type is converted to the type of the 
operand with signed integer type.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Peter II schrieb:
> er geht scheinbar davon aus das
> data[5] * 0x100
>
> unsigned berechnet wird, damit niemals größer als 32768 werden kann,
> damit kann das höchste bit nicht gesetzte sein, damit ist dann der
> vergleich niemals war.

Das entscheidende Stichwort ist das "undefined Behavior":

Ist data[5] <= 127, kann das Produkt maximal 32512 = 0x7f00 werden, das
höchste Bit ist also nicht gesetzt.

Ist hingegen data[5] >= 128, entsteht ein Signed-Integer-Overflow.
Dieser führt zu einem undefined Behavior, bei dem der Compiler von
beliebigen Annahmen ausgehen darf. Schlauerweise nimmt er deswegen an,
dass das höchste Bit auch in diesem Fall nicht gesetzt ist, weil der
damit ein ganzes Stück Code wegoptimieren kann.

Oder er nimmt an, dass der Programmierer den Code so geschrieben hat,
dass es gar nicht erst zum Overflow kommt, was diese Optimierung
ebenfalls rechtfertigt.

Neuere Versionen des GCC nutzen an mehreren Stellen die Regeln für
undefined Behavior und strict Aliasing für aggressive Optimierungen.
Unsauber programmierter Code, der bei früheren GCCs noch funktionierte,
führt jetzt mitunter zu seltsamen Fehlern, bei denen fälschlicherweise
der Verdacht erst einmal auf den Compiler fällt.

: Bearbeitet durch Moderator
von Yalu X. (yalu) (Moderator)


Lesenswert?

Hier ist die Problematik noch einmal auf das Wesentliche reduziert und
für PC-Plattformen dargestellt:

main.c:
1
#include <stdio.h>
2
#include "externfunc.h"
3
4
int internfunc(unsigned char uc) {
5
  unsigned int ui = uc * 100000000;
6
  return ui == 3000000000;
7
}
8
9
int main(void) {
10
  printf("%d\n", internfunc(30));
11
  printf("%d\n", externfunc(30));
12
  return 0;
13
}


externfunc.h:
1
#ifndef EXTERNFUNC_H
2
#define EXTERNFUNC_H
3
4
int externfunc(unsigned char uc);
5
6
#endif


externfunc.c:
1
#include "externfunc.h"
2
3
int externfunc(unsigned char uc) {
4
  unsigned int ui = uc * 100000000;
5
  return ui == 3000000000;
6
}

Die beiden Funktionen internfunc und externfunc sind bis auf den
Funktionsnamen exakt identisch und werden in main mit dem gleichen
Argument aufgerufen. Trotzdem liefern sie bei Optimierungsstufe -O2, -O3
und -Os unterschiedliche Ergebnisse:

1
$ gcc -O2 -o main main.c  externfunc.c 
2
$ main
3
1
4
0

Ist das in Bug im Compiler? Nein, es ist undefined Behavior.

Der Grund für das unterschiedliche Verhalten:

Der Aufruf von internfunc wird geinlinet und das Ergebnis bereits zur
Compilezeit berechnet. Bei dem Produkt entsteht zwar ein Überlauf
(30*100000000=3000000000 ist nicht als signed int darstellbar), der
Compiler behandelt diesen Überlauf aber nicht explizit, sondern
interpretiert das Ergebnis einfach als signed int, d.h. als
30*100000000-2**32=-1294967296. Diese Zahl wird bei der Zuweisung an ui
implizit nach unsigned int gecastet, was wieder 3000000000 ergibt. Somit
ist der anschließende Vergleich wahr und der Rückgabewert 1.

Bei der Übersetzung externfunci.c weiß der Compiler nicht, mit welchem
Argument die Funktion aufgerufen werden wird, er kann also keine
Berechnung zur Compilezeit durchführen. Er geht aber in den höheren
Optimierungsstufen davon aus, das kein undefined Behavior stattfinden
wird, also kann das Produkt selbst bei Berücksichtigung des impliziten
Casts nach unsigned int niemals 3000000000 werden. Also ist der
Rückgabewert immer 0, und die Multiplikation und der Vergleich können
wegoptimiert werden.

In beiden Fällen optimiert der Compiler die Multiplikation und den
Vergleich weg. Da er dies aber (legalerweise) auf unterschiedliche Weise
tut, sind die Funktionsergebnisse ebenfalls unterschiedlich.

: Bearbeitet durch Moderator
von Gastino G. (gastino)


Lesenswert?

Und wenn jetzt einer meint, dass der gcc vor solchen Dingen per 
"-Wunreachable-code" warnt, dann irrt er:

http://gcc.gnu.org/ml/gcc-help/2011-05/msg00360.html

Leider nicht sehr schlau...

Empfehlenswert ist die Nutzung eines statischen Source Code Analyzers.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Gastino G. schrieb:
> Und wenn jetzt einer meint, dass der gcc vor solchen Dingen per
> "-Wunreachable-code" warnt, dann irrt er:

Du kannst Warnungen aktivieren wie -Wconversion:
1
warning: conversion to 'unsigned int' from 'int' may change the sign of the result [-Wsign-conversion]

Weiters gibt es -fsanitize= :
1
-fsanitize=undefined
2
   Enable UndefinedBehaviorSanitizer, a fast undefined behavior
3
   detector. Various computations are instrumented to detect
4
   undefined behavior at runtime. Current suboptions are:

Wer Signed Overflow wie in Yalus Beispiel verwenden will, setzt -fwrapv 
so dass Signed Overflow sich analog verhält wie Unsigned Overflow 
(Wrapping, d.h. modulo 2^bits).


Yalu X. schrieb:
> Trotzdem liefern sie bei Optimierungsstufe -O2, -O3
> und -Os unterschiedliche Ergebnisse:

> 1
> 0

Die Ergebnisse sind gleich, nämlich beide male Undefined Behavior :-)
Mit gcc 4.7 erhalte ich für internfunc etwa 0 als Ergebnis.

> [...] also kann das Produkt selbst bei Berücksichtigung des
> impliziten Casts nach unsigned int niemals 3000000000 werden.

Wie gesagt, wer Signed Overflow mag setzt -fwrapv.

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.