Forum: Mikrocontroller und Digitale Elektronik AVR Inline Optimierung kaputt?


von Einer K. (Gast)


Lesenswert?

Hi ....
Bin beim abarbeiten von Arrays auf ein lustiges Problem gestoßen.
Zwischendurch traten falsche bzw. unerwartet/unmöglich große 
Summen/Ausgaben auf ....


ATMega328P Arduino Testcode, -Os AvrGcc 7.3 , 9.2 , 10.1 (vermutlich 
viele weitere)
1
byte feld[200];
2
3
void setup()
4
{
5
  Serial.begin(9600);
6
7
    
8
  // alle Zellen vorbesetzen
9
  for(byte &data:feld) data = 0xFF;
10
11
  
12
  int result = 0; 
13
  for(byte data:feld) result += data;
14
  Serial.println(result); 
15
16
  // Serial.println(4711); // Kontrollausgabe
17
}
18
19
void loop(){}

> for(byte &data:feld) data = 0xFF;
wird zu memset() optimiert
OK


> for(byte data:feld) result += data;
Hier passiert ein Überlauf.
Muss C++ korrekt abhandeln, tut es auch.
OK

Ausgabe: 4294952760
Falsch!
Zumindest in der Form falsch, als dass 4294952760 gar nicht in AVR int 
passt.

Erwartete Ausgabe: -14536

Mittel die richtige Ausgabe zu erzeugen:

A: volatile int result = 0;

B: irgendwo im Programm, die Zeile
> Serial.println(4711); // Kontrollausgabe
zusätzlich unterbringen

C: niedrigere Optimierungsstufe -O0 oder -O1


-----------
Und ja, das ist Arduino.
Und auch ja, das ist kein Serial oder Print Problem, und damit auch kein 
Arduino Problem.
Es erscheint nur in dem Zusammenhang.

Ja, auch macht der Code keinen unmittelbaren Sinn, außer, den Fehler zu 
zeigen.



Meine bisherige Diagnose sagt:
Beim automatischen inlining wird statt der richtigen Methode
>size_t Print::println(int, int = DEC);
die falsche
> Print::println(unsigned long, int = DEC);
Verwendet.

https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Print.h


Am Rande:
Selbst ein expliziter Cast bleibt ohne Wirkung
> Serial.println((int)result);

von c-hater (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:

>   for(byte &data:feld) data = 0xFF;
             ^

>   for(byte data:feld) result += data;
             ^???

Eins von beidem muss wohl falsch sein...

von Einer K. (Gast)


Lesenswert?

c-hater schrieb:
> Eins von beidem muss wohl falsch sein...

Nee, ist schon korrekt so!
Einmal per Referenz und einmal per Value.

> for(byte data:feld) result += data;
Da könnte man auch mit der Referenz arbeiten
> for(byte &data:feld) result += data;
Macht aber keinen wirksamen Unterschied

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich hatte auch getestet. Es spielt keine Rolle ob man die Kurz- oder 
Langform der for Schleife anwendet. Es lässt derzeit den Schluss zu, 
weil man das nachvollziehen kann, dass bei den Optimierungsleveln etwas 
schief läuft. Mit Zwischenwertausgabe stimmts, da kann er demnach nicht 
so stark optimieren. Ohne Zwischenwert gehts daneben.

Ich hatte es auch außerhalb der Arduino IDE probiert, gleicher Effekt. 
Deswegen erhärtet sich der Verdacht das die gcc Optimierung schief 
läuft. Aus unbekannten Grund wird das int zu unsigned long mit völlig 
falschen Wert, selbst wenn man gewillt ist das Zweierkomplement 
anzuwenden.

Ist nur eine etwas andere Form und auf den fehlerhaften Bereich wo man 
es dann sieht beschränkt.
1
byte x[200];
2
int result;
3
4
void setup()
5
{
6
  Serial.begin(115200);
7
  Serial.println("\nStart");
8
9
  // alle Zellen vorbesetzen
10
  for(byte &data:x) data = 0xFF;
11
  
12
  for(byte i=127; i<133; i++)
13
  {
14
    summieren(i);
15
  }
16
  
17
}
18
19
void loop()
20
{
21
}
22
23
void summieren (const byte count)
24
{
25
  // summe über alle Zellen
26
  result = 0;
27
  for(byte i=0; i<count; i++)
28
  {
29
    result = result + x[i];
30
    //Serial.println(result); // ohne diese Zeile falscher Überlauf
31
  }
32
 
33
  // summe zeigen
34
  Serial.println(result);
35
}

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> c-hater schrieb:
>> Eins von beidem muss wohl falsch sein...
>
> Nee, ist schon korrekt so!
> Einmal per Referenz und einmal per Value.
>
>> for(byte data:feld) result += data;
> Da könnte man auch mit der Referenz arbeiten
>> for(byte &data:feld) result += data;
> Macht aber keinen wirksamen Unterschied

Zeig' mal das *.lst. Erst da kann man sehen, was wirklich passiert.

Was bedeutet eigentlich ":feld"? "feld" sieht mir irgendwie nicht nach 
einem Schlüsselwort der Sprache aus, das würde dann eher "field" heißen. 
Ein Deklaration für "feld" sehe ich aber auch nicht und auch keine 
Stelle, wo das irgendwie benutzt wird.

Irgendeinen Sinn wird es aber haben, sonst würde der Compiler ja 
meckern...

von c-hater (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> c-hater schrieb:
>> Eins von beidem muss wohl falsch sein...
>
> Nee, ist schon korrekt so!
> Einmal per Referenz und einmal per Value.
>
>> for(byte data:feld) result += data;
> Da könnte man auch mit der Referenz arbeiten
>> for(byte &data:feld) result += data;
> Macht aber keinen wirksamen Unterschied

Zeig' mal das *.lst. Erst da kann man sehen, was wirklich passiert.

Was bedeutet eigentlich ":feld"? "feld" sieht mir irgendwie nicht nach 
einem Schlüsselwort der Sprache aus, das würde dann eher "field" heißen. 
Ein Deklaration für "feld" sehe ich aber auch nicht und auch keine 
Stelle, wo das irgendwie benutzt wird.

Irgendeinen Sinn wird es aber haben, sonst würde der Compiler ja 
meckern...

Naja, ich und C++, wir werden niemals Freunde...

von Kosmos (Gast)


Lesenswert?

c-hater schrieb:
> Was bedeutet eigentlich ":feld"?

So heisst sein Array...  siehe erste Codezeile im Eröffnungspost.

von Kaj (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
>> for(byte data:feld) result += data;
> Hier passiert ein Überlauf.
> Muss C++ korrekt abhandeln, tut es auch.
> OK
Wenn result ueberlaeuft hast du UB.

von Einer K. (Gast)


Lesenswert?

Veit D. schrieb:
> mit völlig
> falschen Wert, selbst wenn man gewillt ist das Zweierkomplement
> anzuwenden.

Hmm ....
1
(int)51000            //     -14536
2
(unsigned int)-14536  //      51000
3
(unsigned long)-14536 // 4294952760
4
(int)4294952760       //     -14536
Das passt alles schon.
(zumindest bei mir)

Es ist schon die falsche Methode, welche eingebunden wird.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

sorry, dann hatte ich wohl falsch gerechnet. Ich halte mich erstmal 
raus. Kann im Moment eh nichts weiter machen wie mitlesen und abwarten 
zu welcher Erkenntis andere Programmierer kommen.

von Einer K. (Gast)


Lesenswert?

Kaj schrieb:
> Wenn result ueberlaeuft hast du UB.

Selbst wenn ....

OK, in meinem Beispiel findet der Überlauf statt.

Der eigentliche Punkt ist der, dass der Compiler gar nicht wissen 
kann, dass ein Überlauf stattfindet.
z.B. wenn das Feld mit externen Daten geflutet wird.

Und dennoch bindet er die falsche Methode inline ein.
Da liegt der Hase im Pfeffer.

Der Überlauf dient nur dazu das Problem ans Licht zu bringen.

von c-hater (Gast)


Lesenswert?

Kosmos schrieb:

> So heisst sein Array...  siehe erste Codezeile im Eröffnungspost.

Ah, jetzt ja.

OK, es ist also das, was andere Sprachen sinnvollerweise MENSCHENLESBAR 
mit foreach bauen. C++ ist Klartextverschlüsselung, das zeigt sich hier 
erneut. Aber d'rauf geschissen.

Jedenfalls ist der Fehler dann ganz klar: "result" ist int, 
200*255=51000 ist größer als maxint, also schonmal Überlauf, der war 
aber auch vom Fanboy so erwartet worden. Den genauen Wert habe ich jetzt 
nicht nachgerechnet, wird schon passen.

Falsch ist jedenfalls die Ausgabe. Der Typ von result ist wohldefiniert, 
ob die Scheiße nun überlauft oder nicht, in der Ausgabe darf nur etwas 
erscheinen, was zwischen minint und maxint liegt, ganz egal, ob und wie 
oft der Kram zwischenzeitlich übergelaufen ist.

Also bleibt nur: wo, zum Teufel ist das *.lst-File? Nur das kann die 
Aufklärung bringen, denn nur in Assembler sieht man, was WIRKLICH 
passiert. Und kann daraus dann Rückschlüsse darauf ziehen, was der 
Compiler sich dabei wohl "gedacht" hat...

von c-hater (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:

> OK, in meinem Beispiel findet der Überlauf statt.

Definitiv.

> Der eigentliche Punkt ist der, dass der Compiler gar nicht wissen
> kann, dass ein Überlauf stattfindet.

Doch, da in konkreten Fall alle Ausdrücke bereits zur Compilezeit 
berechenbar sind.

> z.B. wenn das Feld mit externen Daten geflutet wird.

Dann natürlich nicht mehr. Wenn der Compiler die Chance dafür gesehen 
hätte, hätte er nicht die Auswertung zur Compilezeit gemacht.

Aber egal, diese Auswertung zur Compilezeit ist ganz offensichtlich 
fehlerhaft.

von Einer K. (Gast)


Angehängte Dateien:

Lesenswert?

c-hater schrieb:
> das *.lst-File?

Suchst du das im Anhang?

von Einer K. (Gast)


Lesenswert?

c-hater schrieb:
>> z.B. wenn das Feld mit externen Daten geflutet wird.
>
> Dann natürlich nicht mehr. Wenn der Compiler die Chance dafür gesehen
> hätte, hätte er nicht die Auswertung zur Compilezeit gemacht.
>
> Aber egal, diese Auswertung zur Compilezeit ist ganz offensichtlich
> fehlerhaft.

Falsche Annahme!
Es geschieht auch mit "Live" Daten.
(so ist es ja auch erst aufgefallen)

von Peter D. (peda)


Lesenswert?

Arduino Fanboy D. schrieb:
> Hier passiert ein Überlauf.

Für (signed) int ist ein Überlauf undefiniert. Der Compiler darf machen, 
was er will.
Ein Überlauf ist nur für unsigned Typen definiert.

von Einer K. (Gast)


Lesenswert?

Peter D. schrieb:
> Für (signed) int ist ein Überlauf undefiniert. Der Compiler darf machen,
> was er will.
> Ein Überlauf ist nur für unsigned Typen definiert.

Es wird die falsche Methode aufgerufen/eingebunden.
Das ist das Problem.

Der Überlauf hats nur ans Licht gebracht.

Der Fehler persistiert, auch wenn kein Überlauf stattfindet.
Und man darum auch nichts davon sieht.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Der Compiler hat – wie in 99,99% aller zweifelhaften Fälle – recht:

Du startest mit result=0. Dann addierst du 200 nichtnegative Werte (der
Datentyp byte ist unsigned char). Der Compiler, schlau wie er ist,
schließt daraus, dass entweder das Ergebnis nichtnegativ ist oder – im
Fall eines Überlaufs – undefined Behavior vorliegt. Um UB muss er sich
keine Gedanken machen, also geht er von einem nichtnegativen Ergebnis
aus.

Über ein paar Umwege wird mit diesem Ergebnis print(long, int)
aufgerufen, das wie folgt definiert ist:

1
size_t Print::print(long n, int base)
2
{
3
  if (base == 0) {
4
    return write(n);
5
  } else if (base == 10) {
6
    if (n < 0) {
7
      int t = print('-');
8
      n = -n;
9
      return printNumber(n, 10) + t;
10
    }
11
    return printNumber(n, 10);
12
  } else {
13
    return printNumber(n, base);
14
  }
15
}

Da das Argument n nicht negativ sein kann, wird der Abschnitt beginnend
mit if(n<0) wegoptimiert und stattdessen gleich printNumber(unsigned
long, int) aufgerufen.

von c-hater (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:

> Falsche Annahme!

Jepp. Die beiden Schleifen werden korrekt umgesetzt und "result" wird 
auch zur Laufzeit für die Ausgabe benutzt. Also: rein garnix mit 
"Optimierung" bei den Schleifen. Deren Codeeffizienz ist absolut auf 
Asm-Anfänger-Niveau. Denn der würde schon aus reiner Faulheit oder weil 
er es (noch) nicht anders kann, nur ein Byte als Schleifenzähler 
benutzen, der Profi sowieso...

Der Fehler steckt also rein in der Ausgabe. Konkret in:

von c-hater (Gast)


Lesenswert?

c-hater schrieb:

> Der Fehler steckt also rein in der Ausgabe. Konkret in:

Verdammt, da fehlte noch:

Print::println(int, int)

Beitrag #6337459 wurde von einem Moderator gelöscht.
von Einer K. (Gast)


Lesenswert?

c-hater schrieb:
> Print::println(int, int)
Die Methode ist ist schon ok, und tut das was sie soll.

> Print::println(unsigned long, int)
Übrigens auch.

Der Punkt ist, dass mit Optimierung
> Print::println(unsigned long, int)
verwendet wird, und mit unterbundener Optimierung
> Print::println(int, int)


---------

Yalu X. schrieb:
> Da das Argument n nicht negativ sein kann, wird der Abschnitt beginnend
> mit if(n<0) wegoptimiert und stattdessen gleich printNumber(unsigned
> long, int) aufgerufen.

Schön ist das nicht....




Arduino Fanboy D. schrieb:
> Am Rande:
> Selbst ein expliziter Cast bleibt ohne Wirkung
>> Serial.println((int)result);
Dieses halte ich für einen klaren Auftrag
> Print::println(int, int)
zu verwenden.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

mir erschließen sich die Begründungen noch nicht. Wie kann es sein das 
der Compiler ohne Warnung aus einem int ungesehen unsigned long macht? 
Das darf schon nicht sein. Er warnt nicht einmal vor dem drohenden int 
Überlauf. Zudem das Problem abhängig des Optimierungslevels ist.

Was ich generell für gefährlich halte ist genau dieses undefinierte 
Verhalten für (signed) int beim Überlauf. Warum kann der Wertebereich 
nicht einfach überlaufen? Tut er ja hier auch korrekt man er nicht krass 
optimieren kann.

von (prx) A. K. (prx)


Lesenswert?

Yalu hat das völlig zutreffend analysiert.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

er hat es teilweise analysiert, die Gründe warum das so konkret passiert 
sind noch nicht geklärt, weil beim analysieren ein paar Dinge 
un­be­rück­sich­tigt blieben. So einfach kann man das nicht abtun.

von Johannes S. (Gast)


Lesenswert?

A. K. schrieb:
> Yalu hat das völlig zutreffend analysiert.

Sehe ich auch so, es war letztendlich doch das UB.
Ist schon eine versteckte Falle, aber die Lehre daraus ist, integer eben 
nicht überlaufen zu lassen.

von (prx) A. K. (prx)


Lesenswert?

Veit D. schrieb:
> mir erschließen sich die Begründungen noch nicht. Wie kann es sein das
> der Compiler ohne Warnung aus einem int ungesehen unsigned long macht?

Die Summe positiver Werte kann mathematisch nicht negativ werden. 
Überlaufverhalten muss der Compiler nicht berücksichtigen. Das wars, 
Ende und aus.

> Das darf schon nicht sein. Er warnt nicht einmal vor dem drohenden int
> Überlauf. Zudem das Problem abhängig des Optimierungslevels ist.

Natürlich ist es davon abhängig. Der Compiler führt nur mit 
eingeschalteter Optimierung eine Wertebereichsanalyse durch. Und nur 
dann lässt er deshalb die Prüfung auf einen negativen Wert weg, wie man 
im Code sehen kann.

> Was ich generell für gefährlich halte ist genau dieses undefinierte
> Verhalten für (signed) int beim Überlauf.

So ist C definiert, ob es gefällt oder nicht.

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


Lesenswert?

Veit D. schrieb:
> er hat es teilweise analysiert,

Mehr ist nicht nötig. Was fehlt dir?

von Johannes S. (Gast)


Lesenswert?

Aber schon interessant das der Compiler nicht nur das inline statt call 
macht, sondern sogar noch einen Teil der Funktion weglässt.

von (prx) A. K. (prx)


Lesenswert?

Johannes S. schrieb:
> Aber schon interessant das der Compiler nicht nur das inline statt call
> macht, sondern sogar noch einen Teil der Funktion weglässt.

Exakt dies gehört zu den wichtigsten Optimierungen, die es als Folge von 
Inlining überhaupt gibt. Der Compiler kann Information aus dem 
aufrufenden Code in den Code der Funktion einfliessen lassen. So kann 
ein als Konstante übergebener Parameter fundamental kürzeren Asm-Code 
zur Folge haben, gegenüber Code, bei dem der Compiler nichts über den 
Wert weiss.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Undefined Behaviour?

Aber wenn ich ein uint8_t wiederholt um 1 erhöhe, dann kommt nach 255 
immer die 0. Das habe ich schon zig mal Indizes auf Ringpuffer genutzt, 
die genau 256 Bytes groß sind.

Und wenn ich zwei uint32_t Zeitpunkte subtrahiere, wie
> if (millis() - started > 100) puts("100ms sind rum");
Kommt auch immer das richtige Ergebnis heraus, auch wenn der Timer 
zwischendurch einmal übergelaufen ist und die Subtraktion somit 
ebenfalls einen Überlauf macht.

Wenn das mal nicht so wäre, würde ich extrem dumm gucken.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Übrigens:

Wenn man mit result=-1 startet, muss der Compiler auch mit einer
negativen Summe rechnen und wird deswegen die o.g. Optimierung nicht
vornehmen. Ich kann das zwar nicht testen, da ich keinen Arduino habe,
gehe aber davon aus, dass dann -14537 ausgegeben wird. Undefined
Behavior ist das zwar immer noch, aber vielleicht etwas weniger
"unexpected Behavior".


Stefan ⛄ F. schrieb:
> Aber wenn ich ein uint8_t wiederholt um 1 erhöhe, dann kommt nach 255
> immer die 0. Wenn das mal nicht so wäre, würde ich extrem dumm gucken.

Für unsigned ist das auch so spezifiziert. Da wird mit Modulo-Arithmetik
gerechnet, weswegen es keine Überläufe und damit auch kein UB gibt.

von (prx) A. K. (prx)


Lesenswert?

Stefan ⛄ F. schrieb:
> Aber wenn ich ein uint8_t wiederholt um 1 erhöhe, dann kommt nach 255
> immer die 0.

Vorzeichenlose Datentypen haben ein definiertes Überlaufverhalten, Typen 
mit Vorzeichen aber nicht. So sind C und C++ definiert.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

wollte noch niemand ein signed bewusst überlaufen lassen? Nur weil man 
einen positiven Wert addiert darf doch nicht der Datentyp seitens des 
Compilers in Frage gestellt werden.

Mein Verständnisproblem liegt darin, das result bis zur Print Methode 
inkl. deren Aufruf int bleibt und erst danach falsch gewandelt wird. Das 
heißt für mich, dass result beim summieren int bleibt und korrekt 
überläuft. Erst danach wird es Mist. Dafür fehlt mir noch eine Erklärung 
was da passiert.

Jetzt habe ich einmal result mit -1 initialisiert. Müßte dem Compiler 
sagen das ich wirklich signed haben möchte. Er machts wieder falsch gibt 
unsigned long aus.
1
byte x[200];
2
int result = -1;
3
4
void setup()
5
{
6
  Serial.begin(115200);
7
  Serial.println("\nStart");
8
9
  // alle Zellen vorbesetzen
10
  for(byte &data:x) data = 0xFF;
11
  
12
  for(byte i=128; i<131; i++)
13
  {
14
    summieren(i);
15
  }
16
  
17
}
18
19
void loop()
20
{
21
}
22
23
void summieren (const byte count)
24
{
25
  // summe über alle Zellen
26
  result = -1;
27
  for(byte i=0; i<count; i++)
28
  {
29
    result = result + x[i];
30
  }
31
 
32
  // summe zeigen
33
  Serial.println(result);
34
}

von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:
> Wenn man mit result=-1 startet, muss der Compiler auch mit einer
> negativen Summe rechnen und wird deswegen die o.g. Optimierung nicht
> vornehmen.

Es wäre aber zulässig, auf -1 statt <0 zu testen. ;-)

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

Veit D. schrieb:
> Erst danach wird es Mist. Dafür fehlt mir noch eine Erklärung was da
> passiert.

Dazu muss man sich die Print Funktion ansehen, die wird inlined und 
optimiert.

von Stefan F. (Gast)


Lesenswert?

Veit D. schrieb:
> wollte noch niemand ein signed bewusst überlaufen lassen?

Ist ja gut, ich habe es inzwischen auch verstanden.

von c-hater (Gast)


Lesenswert?

A. K. schrieb:

> Die Summe positiver Werte kann mathematisch nicht negativ werden.

Da gebe ich dir vollkommen Recht, das können sie nicht.

> Überlaufverhalten muss der Compiler nicht berücksichtigen. Das wars,
> Ende und aus.

Da gebe ich dir nicht Recht. Den Datentyp von "result" darf der Compiler 
nicht eigenmächtig erweitern. Niemals. Denn das konterkarriert das 
gesamte Konzept von Datentypen. Man kann sie dann auch gleich ganz 
weglassen und ist wieder beim schönen alten Assembler...

> So ist C definiert, ob es gefällt oder nicht.

Und da fragen Leute, wie man dazu kommen kann, C so abgrundtief zu 
hassen, wie ich das tue...

Der Punkt ist: sowas wie UB darf es in einer richtigen Hochsprache 
einfach nicht geben. Dafür ist sie da. Für den Rest geht auch 
Assembler...

von Stefan F. (Gast)


Lesenswert?

Veit D. schrieb:
> int result = -1;
> result = result + x[i];

> Er machts wieder falsch gibt unsigned long aus.

Das ist echt schräg, für mich ist das absolut sonnenklar eine (signed) 
Integer Operation.

Und das natürlich auch:
>  Serial.println(result);

von Veit D. (devil-elec)


Lesenswert?

Hallo,

mit result Initialisierung und lokaler Zuweisung auf '-2' werden falsche 
Zeichen ausgegeben, sowas hier:  -⸮
Erst ab -10 stimmts wieder.

von (prx) A. K. (prx)


Lesenswert?

Veit D. schrieb:
> wollte noch niemand ein signed bewusst überlaufen lassen? Nur weil man
> einen positiven Wert addiert darf doch nicht der Datentyp seitens des
> Compilers in Frage gestellt werden.

UD ist genau das: undefiniert. Als doch, er darf. Wenn mit Vorzeichen.

> Mein Verständnisproblem liegt darin, das result bis zur Print Methode
> inkl. deren Aufruf int bleibt und erst danach falsch gewandelt wird. Das
> heißt für mich, dass result beim summieren int bleibt und korrekt
> überläuft.

Es läuft über, aber es läuft nicht "korrekt" über, weil es bei Typen mit 
Vorzeichen per Sprachdefinition keinen korrekten Überlauf gibt.

Der Compiler analysiert bei aktivem Optimizer bei jeder Operation, wie 
der Wertebereich des Ergebnisses beschaffen sein kann. Diese Analyse 
sagt ihm, dass "result" nie negativ wird. Er verhält sich dabei aber 
nicht wie der AVR Prozessor, weil er das nicht tun muss.

von Einer K. (Gast)


Lesenswert?

Yalu X. schrieb:
> Wenn man mit result=-1 startet, muss der Compiler auch mit einer
> negativen Summe rechnen und wird deswegen die o.g. Optimierung nicht
> vornehmen. Ich kann das zwar nicht testen, da ich keinen Arduino habe,
> gehe aber davon aus, dass dann -14537 ausgegeben wird.

Der Test ist schnell gemacht!
result: 4294952759

von MaWin O. (mawin_original)


Lesenswert?

c-hater schrieb:
> Den Datentyp von "result" darf der Compiler
> nicht eigenmächtig erweitern. Niemals.

Ja doch. Natürlich darf er das, wenn alle Werte in den neuen Datentyp 
passen.
Was hier der Fall ist, da result nie negativ sein kann.

von (prx) A. K. (prx)


Lesenswert?

c-hater schrieb:
> Da gebe ich dir nicht Recht. Den Datentyp von "result" darf der Compiler
> nicht eigenmächtig erweitern. Niemals.

Das tut er genau dort, wo es im Quelltext exakt so drinsteht. Nämlich im 
Code von println, dessen "int" Variante auf einer breiteren Variante 
beruht. An dieser Stelle steht im Quellcode die Erweiterung auf 32 Bits. 
Die Verarsche kommt nur daher, dass er den Code, der negative Werte 
gesondert behandelt, aufgrund seiner Wertebereichsanalyse schlicht 
weglässt.

Man sollte sich ausmalen, dass es zwei verschiedene Paar Stiefel sind, 
was der erzeugte Code für Werte produziert, und was der Compiler davon 
annimmt. Der Code produziert negative Werte, der Compiler indes geht 
davon aus, dass die positiv sind. Das klingt etwas gespalten, ist hier 
aber zulässig.

Man kann C/C++ gut oder schlecht finden, aber wenn man nicht vorhat, 
eine eigene Sprache zu definieren, sondern die vorhandene nutzen will, 
ist der Standard stets stärker als der eigene Wunsch.

von c-hater (Gast)


Lesenswert?

MaWin O. schrieb:

> Ja doch. Natürlich darf er das, wenn alle Werte in den neuen Datentyp
> passen.
> Was hier der Fall ist, da result nie negativ sein kann.

Das ist doch Unsinn. Der Programmierer hat doch den Datentyp explizit 
vorgegeben. Und zwar als einen Typ der erstens (auf der Zielarchitektur) 
16 Bit umfasst und zweitens "signed" ist.

Die explizite Willensäußerung des Programmierers wird hier also von der 
subalternen Compilerlogik mehr als grob mißachtet.

Einer Sprache, die sowas macht, ist absolut nicht zu trauen... 
Niemals... Unter keinen Umständen...

von Stefan F. (Gast)


Lesenswert?

c-hater schrieb:
> Einer Sprache, die sowas macht, ist absolut nicht zu trauen...
> Niemals... Unter keinen Umständen...

Da hat er Futter deluxe bekommen, da fängt er gleich das sabbern an.

von MaWin O. (mawin_original)


Lesenswert?

c-hater schrieb:
> Einer Sprache, die sowas macht, ist absolut nicht zu trauen...
> Niemals... Unter keinen Umständen...

Richtig.
Wenn man die Regeln nicht kennt.

Ich kann auch einfach annehmen, dass irgendwelche asm-Instruktionen 
irgendwas machen, was ich mir so ausgedacht/angenommen habe. Ob das mit 
der Realität übereinstimmt, braucht mich ja nicht zu interessieren. Es 
sei denn, ich will ein korrektes Programm haben.

von c-hater (Gast)


Lesenswert?

c-hater schrieb:

> Die explizite Willensäußerung des Programmierers wird hier also von der
> subalternen Compilerlogik mehr als grob mißachtet.

Ergänzend noch: Das passiert obendrein ohne tatsächlich eine 
nennenswerte Optimierung darzustellen. Was tatsächlich optimiert werden 
könnte, bleibt auf Asm-Einsteigerniveau...

Das kann sich doch nur um einen schlechten Scherz handeln. Ich habe 
weder die Typsicherheit, die ich mir von einer echten Hochsprache 
verspreche noch auch nur näherungsweise optimalen Code...

von (prx) A. K. (prx)


Lesenswert?

c-hater schrieb:
> Die explizite Willensäußerung des Programmierers wird hier also von der
> subalternen Compilerlogik mehr als grob mißachtet.

So kann man das sehen. Aber es ist nicht dem Compiler anzulasten, 
sondern der Sprachdefinition. Und die ist nicht subaltern, sondern 
äusserst dominant.

von Stefan F. (Gast)


Lesenswert?

Ein bisschen Recht hat der c-hater schon. Bei jeder anderen 
Programmiersprache hätten wir schon einen Bug Report ausgefüllt, währen 
die C Benutzer diskutieren, ob das überhaupt ein Bug ist oder ein 
unerwartetes Feature.

von MaWin O. (mawin_original)


Lesenswert?

Es ist übrigens völlig egal, ob der Compiler potentiell sehen kann, dass 
UB auftreten wird.
Sobald UB tatsächlich während der Laufzeit auftritt, ist der weitere 
Programmablauf undefiniert.
Deshalb spielt es überhaupt keine Rolle, woher die Werte kommen.

Im Umkehrschluss darf der Compiler selbstverständlich annehmen, dass UB 
niemals auftritt.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich habe da noch Bauchschmerzen. Beim programmieren immer mit 
mathematischen Begründungen zu kommen halte ich für nicht Zielführend. 
Bauchweh.

Wenn man aus dem negativen Wertebereich kommt müssen auch negative Werte 
zulässig sein. Wenigstens die signed Variable. Rein mathematisch 
betrachtet kann es nach verlassen des negativen Bereiches nie wieder 
negativ werden. Das ist korrekt. Der Compiler kann aber gar nicht wissen 
wann der negative Bereich verlassen wird, da der Überlauf zur Laufzeit 
passiert. Hatte ich bis -10 getestet.

Nur haben wir es hier mit Programmieren zu tun und Datentypen die einen 
definierten Wertebereich haben. Ich als Programmierer erwarte immer das 
der Wert beim Überlauf immer an den Anfang des Wertebereichs fällt. 
Alles andere ist Wahnsinn. Bisher hats auch immer funktioniert. Ich 
konnte bis heute kein undefined Behavior feststellen. Das undefinierte 
Verhalten ist hier einfach falsch.

Was ich nicht nachvollziehen kann ist, warum das undefined Behavior 
verteidigt wird, anstatt zu sagen es sollte geändert werden. Ich bin da 
übrigens voll der Meinung von c-hater.

Ansonsten muss man die Frage stellen dürfen warum mit unsigned 
Datentypen der Wert bei Überlauf auf 0 zurückfällt? Kann mathematisch 
nicht korrekt sein. Kleiner werden geht mathematisch nicht. Korrekt wäre 
am Ende des Wertebereichs stehen zu bleiben. Als Programmierer rechne 
ich natürlich fest damit das unsigned auf Anfang überläuft.

Desweiteren, bei allen Betrachtungen, gibts ja nicht einmal eine 
Überlaufwarnung.

Mir ist bewusst das hier unterschiedlichste Erfahrungen, Sichtweisen und 
Meinungen zusammenprallen, deswegen lasse ich das erstmal sacken für 
hoffentlich weitere klare Gedankengänge.

von (prx) A. K. (prx)


Lesenswert?

Stefan ⛄ F. schrieb:
> Da hat er Futter deluxe bekommen, da fängt er gleich das sabbern an.

Lass ihn sabbern. Wichtiger wäre, ob dem TE der Groschen fiel. Der 
will nämlich in C/C++ programmieren und sollte aus diesem recht 
schönen Fall lernen können.

von Veit D. (devil-elec)


Lesenswert?

Man sollte jetzt nicht unnötig provozieren!

von (prx) A. K. (prx)


Lesenswert?

Veit D. schrieb:
> Was ich nicht nachvollziehen kann ist, warum das undefined Behavior
> verteidigt wird, anstatt zu sagen es sollte geändert werden.

Legendäre Worte des grossen Philosophen Donald Rumsfeld: You program 
with the language you have, not the language you might want or wish to 
have at a later time.

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:

> Da hat er Futter deluxe bekommen, da fängt er gleich das sabbern an.

Ich könnte seitenweise über Fehler (oder auch "Nichtfehler") des gcc und 
anderer C-Compiler schreiben, die mir in meinem Job über den Weg 
gelaufen sind und schlicht dafür gesorgt haben, dass das Programm nicht 
funktioniert hat. Das wenigste davon war von mir selbst geschrieben, ich 
komme gewöhnlich zum Einsatz, wenn die C/C++-Frickler am Ende mit ihrem 
Latein sind...

Aber klar, ist schön, sowas mal ganz genüßlich "live" zu zeigen, anhand 
eines realen nichtsynthetischen Falles. Denn die realen Fälle darf ich 
natürlich typischerweise nicht veröffentlichen...

Nun könnten ja die C/C++-Apologenten sagen: ja, wenn man halt die 
Programmiersprache abseits ihrer Definition benutzt, dann taugen die 
Programmierer nix.

Da würde ich sogar zustimmen. Gebe aber zu bedenken: es handelt sich um 
die überwältigende Mehrheit selbiger...

Was mich zu der Behauptung bringt: die Sprache selber ist einfach 
SCHEISSE!

von c-hater-hater (Gast)


Lesenswert?

c-hater schrieb:
>> So heisst sein Array...  siehe erste Codezeile im Eröffnungspost.
>
> Ah, jetzt ja.
>
> OK, es ist also das, was andere Sprachen sinnvollerweise MENSCHENLESBAR
> mit foreach bauen. C++ ist Klartextverschlüsselung, das zeigt sich hier
> erneut. Aber d'rauf geschissen.

Aha. Ob es nun for (...:...) oder foreach(...) heißt, ändert was genau, 
dass du dich mal wieder blamierst, weil du nicht mal die erste Zeile 
liest?

Stefan ⛄ F. schrieb:
> Ein bisschen Recht hat der c-hater schon. Bei jeder anderen
> Programmiersprache hätten wir schon einen Bug Report ausgefüllt, währen
> die C Benutzer diskutieren, ob das überhaupt ein Bug ist oder ein
> unerwartetes Feature.

Man kann Teile des Sprachstands mögen oder auch nicht, sie sind nun mal 
Teile des Sprachstandards. Weder Bug noch Feature.

von S. R. (svenska)


Lesenswert?

MaWin O. schrieb:
> Sobald UB tatsächlich während der Laufzeit auftritt,
> ist der weitere Programmablauf undefiniert.

Nicht ganz. Sobald UB tätsächlich während der Laufzeit auftritt, war der 
gesamte Programmablauf undefiniert. Also auch die Zeit vor dem UB.

Veit D. schrieb:
> Der Compiler kann aber gar nicht wissen wann der negative Bereich
> verlassen wird, da der Überlauf zur Laufzeit
> passiert. Hatte ich bis -10 getestet.

Ein Laufzeit-UB macht den gesamten Programmablauf ungültig. Wenn der 
Compiler also garantieren kann, dass ein UB auftreten wird, dann kann 
er direkt ungültigen Code erzeugen.

von Johannes S. (Gast)


Lesenswert?


von Stefan F. (Gast)


Lesenswert?

c-hater schrieb:
> Was mich zu der Behauptung bringt: die Sprache selber ist einfach
> SCHEISSE!

Wenn sie super wäre, hätten wir jetzt nicht so viele alternativen. Blöd 
ist nur, dass von den vielen Alternativen auf µC nur wenige in Frage 
kommen und davon wiederum nur wenige (kostengünstig bis kostenlos) 
verfügbar sind.

Dann nimmt man halt, was man kriegen kann.

Oder schnitzt sich seine Bauklötze selber, so wie du.

von MaWin O. (mawin_original)


Lesenswert?

Veit D. schrieb:
> Was ich nicht nachvollziehen kann ist, warum das undefined Behavior
> verteidigt wird, anstatt zu sagen es sollte geändert werden.

Weil UB eine der zentralen Freiheiten des Optimizers ist.
Ohne UB könnte der wesentlich schlechter optimieren. Auch an "normalen" 
Stellen.

Veit D. schrieb:
> Ich als Programmierer erwarte immer das
> der Wert beim Überlauf immer an den Anfang des Wertebereichs fällt.

Ist halt falsch, bei Typen mit Vorzeichen.
Das ist doch jetzt keine komplizierte Regel.

Veit D. schrieb:
> gibts ja nicht einmal eine Überlaufwarnung.

Weil der Compiler das oft nicht wissen kann zur Compilezeit.
Stelle dir z.B. ein if(x) {} vor, in dessen {Body} es zu UB kommen kann, 
wenn x false ist. Soll der Compiler eine Warnung ausgeben, obwohl er 
nicht weiß, was x sein kann?
Selbst wenn er weiß, was x sein kann, kann er nicht wirklich eine 
Warnung ausgeben. Das würde in tausenden Warnings resultieren, weil 
solcher Code üblich ist z.B. in Makros. Der Compiler optimiert es 
stattdessen weg. Und das ist genau das, was man meistens will. Außer man 
halt halt fehlerhaften Code geschrieben.

Es gibt aber ubsan. Der gibt dir Warnungen zur Laufzeit aus.

UB ist halt ein Teil von C.
Man muss es nicht mögen, aber es ist nicht schwer zu verstehen.

Beitrag #6337565 wurde von einem Moderator gelöscht.
von MaWin O. (mawin_original)


Lesenswert?

S. R. schrieb:
> Nicht ganz. Sobald UB tätsächlich während der Laufzeit auftritt, war der
> gesamte Programmablauf undefiniert. Also auch die Zeit vor dem UB.

Ja gut.

Beitrag #6337570 wurde von einem Moderator gelöscht.
von c-hater (Gast)


Lesenswert?

S. R. schrieb:

> Ein Laufzeit-UB macht den gesamten Programmablauf ungültig. Wenn der
> Compiler also garantieren kann, dass ein UB auftreten wird, dann kann
> er direkt ungültigen Code erzeugen.

Nein, dann hat er natürlich direkt einen Fehler zu werfen und darf 
natürlich überhaupt keinen Code erzeugen!

Alles andere wäre völlig kontraproduktiver Schwachsinn!

von MaWin O. (mawin_original)


Lesenswert?

c-hater schrieb:
>> Ein Laufzeit-UB macht den gesamten Programmablauf ungültig. Wenn der
>> Compiler also garantieren kann, dass ein UB auftreten wird, dann kann
>> er direkt ungültigen Code erzeugen.
>
> Nein, dann hat er natürlich direkt einen Fehler zu werfen und darf
> natürlich überhaupt keinen Code erzeugen!

Das ist ein völlig akademisches Problem.
Der Compiler kann praktisch nie beweisen, dass ein Programm definitiv in 
UB laufen wird. Nur bei völlig trivialen Programmen oder z.B. UB direkt 
zu Beginn von main(). Dafür extra einen Warnmechanismus zu 
implementieren, ist Unsinn.

Er kann nur Teile rauswerfen, die garantiert in UB laufen, wenn sie 
denn aufgerufen werden würden. Was sie ja nicht dürfen per Definition. 
Also können sie raus.

von Einer K. (Gast)


Lesenswert?

A. K. schrieb:
> Wichtiger wäre, ob dem TE der Groschen fiel. Der
> will nämlich in C/C++ programmieren und sollte aus diesem recht
> schönen Fall lernen können.

Ach....
Da mache dir mal keine Sorgen!

Klar war mir schon lange und ewig, dass einem ein unerwarteter int 
Überlauf derbe ins Essen spucken kann.

Jetzt habe ich gelernt, dass ein Beabsichtigter das auch kann.
Und zwar auf eine doch recht subtile Art und Weise.

Waren ja auch ein paar Bedingungen für nötig, dass das überhaupt 
auffällig wurde.

Klar, habe ich schon ein wenig Rückschau betrieben...
unsigned Überläufe nutze ich häufiger mal.
signed, glaube bisher nicht....

Wie auch immer...
Wenn man einmal kapiert hat, wie die Falle riecht, dann tappt man 
nächstes mal nicht so leicht hinein.


Meinen Dank!

von c-hater (Gast)


Lesenswert?

MaWin O. schrieb:

> Er kann nur Teile rauswerfen, die garantiert in UB laufen, wenn sie
> denn aufgerufen werden würden. Was sie ja nicht dürfen per Definition.
> Also können sie raus.

Lach.

Witzigerweise könnte er genau das in gezeigtem Beispiel tatsächlich tun. 
Denn alles an diesem Code ist zur Compilezeit berechenbar.

Genau genommen dürfte bei einer wirklichen Optimierung also nur maximal 
4x ldi und einmal rcall rauskommen oder halt eine Fehlermeldung wegen 
UB, wenn der Optimizer die Sache wirklich ernst nimmt.

von MaWin O. (mawin_original)


Lesenswert?

c-hater schrieb:
> Witzigerweise könnte er genau das in gezeigtem Beispiel tatsächlich tun.

Ja. Wie gesagt. Bei trivialen Programmen könnte man eine Fehlermeldung 
ausgeben.
Aber das wäre ein enormer Aufwand für einen akademischen Fall.
Sobald das Programm auch nur etwas komplexer wird, würde die Meldung 
nicht mehr angezeigt werden können.

von Stefan F. (Gast)


Lesenswert?

Da wäre doch mal interessant, was ein anderer Compiler daraus macht. 
Kann man Arduino Projekte mit Keil oder IAR compilieren? Machen die es 
besser?

von c-hater (Gast)


Lesenswert?

MaWin O. schrieb:

> Ja. Wie gesagt. Bei trivialen Programmen könnte man eine Fehlermeldung
> ausgeben.

Nun, der Compiler hat sich entscheiden diese nicht zu tun. Kann man 
vielleicht noch akzeptieren.

Er hat sich aber außerdem entschieden, die offensichtlichste Optimierung 
(alles außer der Ausgabe läßt sich zur Compilezeit berechnen) nicht zu 
nutzen.

Und er hat sich dafür entscheiden, Bullshit-Code mit Null Nutzen zu 
produzieren.

Irgendwie scheint mir dann doch, dass das kein wünschenswertes Ergebnis 
sein kann...

von Rainer V. (a_zip)


Lesenswert?

Stefan ⛄ F. schrieb:
> Da wäre doch mal interessant, was ein anderer Compiler daraus
> macht.
> Kann man Arduino Projekte mit Keil oder IAR compilieren? Machen die es
> besser?

Habe gespannt mitgelesen. Allerdings erschließt sich bisher für mich 
nicht, wo denn jetzt der Fehler liegt! Ist der Programmcode falsch oder 
baut der Compiler wirklich Mist?
Sorry, ich bin kein "Hochsprachler", schon gar nicht im Bereich 
Controller, also seid nicht allzu streng :-)
Gruß Rainer

von MaWin O. (mawin_original)


Lesenswert?

c-hater schrieb:
> Und er hat sich dafür entscheiden, Bullshit-Code mit Null Nutzen zu
> produzieren.

Er hat Bullshit-Binärcode passend zu Bullshit-C-Code produziert.
Bei UB kann der Compiler machen, was er will. Alles ist ein valides 
Ergebnis.

Warum optimiert er hier nicht alles weg?
Ganz einfach. Weil ihm niemand das einprogrammiert hat.
Warum sollte man es auch tun?
Der Compiler kann nur sinnvolle Dinge korrekt optimieren.
Bei Unsinn kommt dann halt Unsinn raus.

von MaWin O. (mawin_original)


Lesenswert?

Rainer V. schrieb:
> Ist der Programmcode falsch

Ja. Er lässt ein signed int überlaufen.

von c-hater (Gast)


Lesenswert?

Rainer V. schrieb:

> Allerdings erschließt sich bisher für mich
> nicht, wo denn jetzt der Fehler liegt! Ist der Programmcode falsch oder
> baut der Compiler wirklich Mist?

Rein formal ist der Programmcode falsch.

Allerdings ist diese "Formalität" so sehr an den Anforderungen der 
Realität vorbei konstruiert, dass man das Konzept der Sprache an sich in 
Frage stellen muss.

So könnte man das wohl zusammenfassen.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Arduino Fanboy D. schrieb:
> Yalu X. schrieb:
>> Wenn man mit result=-1 startet, muss der Compiler auch mit einer
>> negativen Summe rechnen und wird deswegen die o.g. Optimierung nicht
>> vornehmen. Ich kann das zwar nicht testen, da ich keinen Arduino habe,
>> gehe aber davon aus, dass dann -14537 ausgegeben wird.
>
> Der Test ist schnell gemacht!
> result: 4294952759

Hmm, das hätte ich nicht erwartet, auch wenn es wegen des UBs nicht
direkt falsch ist. Aber welche Logik bringt den Compiler dazu, so zu
handeln?

Ich habe den erzeugten Assemblercode mal etwas analysiert ...

A. K. schrieb:
> Es wäre aber zulässig, auf -1 statt <0 zu testen. ;-)

Den Smiley hättest du weglassen können, denn genau das tut der Compiler:

Wenn die Summe gleich -1 ist, wird auch -1 ausgegeben und zwar direkt
das Zeichen '-' gefolgt vom String "1", ohne die Zahl lange von binär
nach dezimal zu konvertieren.

Wenn die Summe ungleich -1 ist, kann sie nur ≥0 sein, so das sie mit der
Funktion printNumber für vorzeichenlose Zahlen ausgegeben werden kann.
Deswegen werden auch die durch UB zustandegekommenen -14537 – anders als
von mir ursprünglich erwartet – vorzeichenlos als 4294952759 ausgegeben.


Jetzt bin ich gerade selber völlig baff, wie gnadenlos der Compiler
solche Dinge optimiert. Aber auf jeden Fall macht er alles richtig.

Und UB ist halt UB, weswegen man es unbedingt vermeiden und nicht darauf
hoffen sollte, dass es vom Compiler schon irgendwie weggebügelt werden
wird.

von MaWin O. (mawin_original)


Lesenswert?

c-hater schrieb:
> Allerdings ist diese "Formalität" so sehr an den Anforderungen der
> Realität vorbei konstruiert, dass man das Konzept der Sprache an sich in
> Frage stellen muss.

Und deshalb gibt es heute Sprachen, die das besser machen.
Trotzdem ist C oft noch das Mittel der Wahl, weil es immer noch die 
breiteste Unterstützung an Compilern hat. Und an Entwicklern. Oder wer 
kann z.B. Rust?

von MaWin O. (mawin_original)


Lesenswert?

Yalu X. schrieb:
> Jetzt bin ich gerade selber völlig baff, wie gnadenlos der Compiler
> solche Dinge optimiert. Aber auf jeden Fall macht er alles richtig.

Ja. Sich öfter mal den generierten Assemblycode angucken, öffnet einem 
die Augen. Insbesondere, wenn man solche Techniken wie LTO nutzt.
Da werden Dinge optimiert, die man niemals für möglich gehalten hätte.
Das inline-Keyword kann man sich zum Beispiel mit LTO zu 99.9% sparen. 
Nur ein Beispiel von vielen.
Etliche Krücken, die man früher brauchte um optimalen Code zu 
generieren, sind heute überflüssig.

Eine Bedingung ist natürlich, dass man korrekten Code ohne UB schreibt.

von S. R. (svenska)


Lesenswert?

c-hater schrieb:
>> Ein Laufzeit-UB macht den gesamten Programmablauf ungültig. Wenn der
>> Compiler also garantieren kann, dass ein UB auftreten wird, dann kann
>> er direkt ungültigen Code erzeugen.
>
> Nein, dann hat er natürlich direkt einen Fehler zu werfen und darf
> natürlich überhaupt keinen Code erzeugen!

Ich möchte deinen Compiler sehen, der das tut.
Achso, den gibt es natürlich nicht, weil du... ach lassen wir das.

Alternativ kannst du mit ubsan bauen und bekommst dann ein abort(), wenn 
UB zur Laufzeit auftritt. Kostet natürlich geringfügig Performance.

> Alles andere wäre völlig kontraproduktiver Schwachsinn!

Tja, gib uns allen eine bessere Programmiersprache und wir wären alle 
glücklich. Nein, nicht Assembler.

von Peter D. (peda)


Lesenswert?

Das Problem tritt ja nur deshalb auf, weil in C++ verschiedene 
Funktionen überlagert werden und der Compiler bei der Auswahl patzt. In 
plain C gibt man mit "%u" das Format vor und alles ist in Butter.
Die höhere Komplexität von C++ bedingt eben auch mehr mögliche 
Fehlerquellen.

Man kann sich ja eine Funktion schreiben, die 2 signed Operanden erhält 
und signed zurück gibt. In der Funktion wird dann nach unsigned gecastet 
und addiert. Dann gibt es kein UB.

von MaWin O. (mawin_original)


Lesenswert?

Peter D. schrieb:
> und der Compiler bei der Auswahl patzt. In
> plain C gibt man mit "%u" das Format vor und alles ist in Butter.

nö, falsch.
Es tritt weiterhin UB auf.

von Nop (Gast)


Lesenswert?

Gib dem Compiler mal die Option -fwrapv mit. GCC nimmt dann bei signed 
overflow an, daß es sich um das Zweierkomplement handelt. Kann nützlich 
sein, wenn man das braucht - obwohl im Allgemeinen beim Auftreten von 
signed overflow der natürlich Quelltext fehlerhaft ist.

von Peter D. (peda)


Lesenswert?

Letztendlich stellt sich die Frage, gibt es irgendeine praktische 
Anwendung, wo ein Überlauf von 127 auf -128 sinnvoll ist?

von Johannes S. (Gast)


Lesenswert?

hat mal jemand den von mir verlinkten Stackoverflow link gelesen? Da 
gibts einen weiteren Link auf einen guten Blog:
https://www.airs.com/blog/archives/120

Der gcc macht diese Optimierungen schon seit 20 Jahren.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Peter D. schrieb:
> Das Problem tritt ja nur deshalb auf, weil in C++ verschiedene
> Funktionen überlagert werden und der Compiler bei der Auswahl patzt.

Nein, das war nur die ursprüngliche Annahme des TE. Die eigentliche
Ursache des unerwarteten Verhaltens liegt darin, dass der Compiler den
Code zur Sonderbehandlung negativer Zahlen wegoptimiert, weil er
nachweisen kann, dass er auszugebende Wert ohne Regelverstoß nicht
negativ werden kann.

> In plain C gibt man mit "%u" das Format vor und alles ist in Butter.

Aber nur deswegen, weil printf i.d.R. vorkompiliert in der Bibliothek
vorliegt und deswegen vom Compiler nicht weiter optimiert werden kann.
Wäre der Code von printf inlinable, würde ein schlauer Compiler dieselbe
Optimierung vornehmen wie im C++-Programm des TE, was das gleiche
unerwartete Verhalten zur Folge hätte.

> Man kann sich ja eine Funktion schreiben, die 2 signed Operanden
> erhält und signed zurück gibt. In der Funktion wird dann nach unsigned
> gecastet und addiert. Dann gibt es kein UB.

Auch die Konvertierung von negativen Werten in unsigned ist UB.

von Nop (Gast)


Lesenswert?

Yalu X. schrieb:

> Auch die Konvertierung von negativen Werten in unsigned ist UB.

Das kann man mit memcpy implementieren. Der memcpy-Aufruf wird dabei 
wegoptimiert.

von Peter D. (peda)


Lesenswert?

Yalu X. schrieb:
> Auch die Konvertierung von negativen Werten in unsigned ist UB.

Dafür funktioniert es aber erstaunlich gut.
Wenn ich eine unsigned Variable mit -1 initialisiere, ist sie je nach 
Typ 0xFF, 0xFFFF oder 0xFFFFFFFF. Es gibt nichtmal ne Warnung.

Beitrag #6337859 wurde von einem Moderator gelöscht.
von (prx) A. K. (prx)


Lesenswert?

Peter D. schrieb:
> Yalu X. schrieb:
>> Auch die Konvertierung von negativen Werten in unsigned ist UB.
>
> Dafür funktioniert es aber erstaunlich gut.

Völlig irrelevant. UB bedeutet nicht, dass dir dein AVR zwingend ins 
Gesicht explodiert. Der Code kann sich so verhalten, wie du es 
erwartest, kann sich aber auch völlig anders verhalten. Und das kann 
sich abhängig von der Version des Compilers, der Optionen und der 
Tagesform ändern.

von (prx) A. K. (prx)


Lesenswert?

In C war von Anfang an die Option enthalten, es auf auf Hardware zu 
implementieren, die im Einerkomplement arbeitet. Das war zur damaligen 
Zeit noch durchaus verbreitet. Da ist das Verhalten bei Überlauf von 
Werten mit Vorzeichen schlicht anders als beim heute üblichen 
Zweierkomplement.

Diese Option wurde m.W. bis heute nicht aus dem Standard gestrichen, 
zumindest ist sie bei C11 noch drin und der Mindestwertebereich von z.B. 
signed char ist deshalb weiterhin -127..+127.

Es gibt (und gab) Hardware, die bei manchen arithmetischen Operationen 
bei einem Überlauf eine Exception auslöst. Es kann auch sinnvoll sein, 
das zu nutzen.

Auf die Einschränkungen bei der Code-Optimierung, die eine Orientierung 
am real stattfindenden Overflow zur Folge haben kann, wurde in Links 
bereits hingewiesen.

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


Lesenswert?

Peter D. schrieb:
> Yalu X. schrieb:
>> Auch die Konvertierung von negativen Werten in unsigned ist UB.
>
> Dafür funktioniert es aber erstaunlich gut.

Man kann sich natürlich beliebige Tricks einfallen lassen, um den
Compiler bei der Optimierung zu behindern. Aber rechne einfach mal
damit, dass der GCC in der nächsten oder übernächsten Version auch
deinen Unsigned-Cast-Trick durchschaut und wirkungslos macht ;-)

von (prx) A. K. (prx)


Lesenswert?

-ftrapv
This option generates traps for signed overflow on addition, 
subtraction, multiplication operations.

-fwrapv
This option instructs the compiler to assume that signed arithmetic 
overflow of addition, subtraction and multiplication wraps around using 
twos-complement representation. This flag enables some optimizations and 
disables others.

The options -ftrapv and -fwrapv override each other, so using -ftrapv 
-fwrapv on the command-line results in -fwrapv being effective. Note 
that only active options override, so using -ftrapv -fwrapv -fno-wrapv 
on the command-line results in -ftrapv being effective.

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

Nop schrieb:
> Gib dem Compiler mal die Option -fwrapv mit.
Done!

Ergebnis sieht wie erwartet aus!
result: -14536

Gut zu wissen.
Meinen Dank...

Aber ob es jemals zum Einsatz kommt?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich habe ja nun mittlweile verstanden das Überlauf mit signed UB ist. 
Aber nur deshalb weil das so vorgesehen ist. Ich komme jedoch mit der 
Begründung nicht klar. Wenn man unsigned korrekt überlaufen lässt warum 
dann nicht auch signed? Ich meine wir Programmierer denken doch in 
Wertebereichen, wir geben Datentypen nicht zum Spass exakt vor. Dann 
erwarte ich eigentlich das sich mein Wert immer in dem vorgesehen 
Wertebereich befindet. Das ist jederzeit logisch nachvollziehbar. Das 
ist in meinen Augen ein ganz simpler Anspruch an einen Compiler. Zudem 
es ja gewöhnlich funktioniert wie erwartet. Wenn genau das aber Zufall 
ist, dann ist das ganz doof. Ich kann mich mit dem UB nicht anfreunden. 
Mir kommt das so vor als wenn man keine Lust hatte für eine korrekte 
Behandlung und definiert das einfach als UB -> Problem abgewälzt. 
Deswegen verstehe ich eure Haltung auch nicht das so zu aktzeptieren wie 
es ist.

Das Problem liegt ja genau in der Optimierung. Hindert man den Compiler 
daran durch ganz banale Zeilen, wie hier gezeigt, dann klappt es. Wenn 
signed Überlauf aber UB ist, dann sollte er doch immer falsch rechnen? 
Okay UB heißt nicht falsch rechnen, man weiß nur nicht was der Compiler 
macht, weil er alles machen darf. Das finde ich blöd.

Würde bedeuten man kann bspw. kein Lauflicht mit signed char bauen. 
Indem man davon ausgeht das der Wert nach +127 auf -128 springt. Nur 
wird das in der Regel praktisch funktionieren, weil man sicherlich Code 
schreibt der nicht explizit den Fehler zeigt, sondern wohl ein wenig 
mehr mit dem Wert rechnen muss und deshalb nicht extrem optimiert werden 
kann an der Stelle. Die übernächste Compilerversion kann aber schon 
wieder schlauer sein.

Dazu gleich noch eine Frage. Habt ihr sämtliche Compilerdoku im Kopf? 
Ich bin froh wenn ich Programmierbücher verstanden habe. Wenn ich dann 
noch in Abhängigkeit auf die Compilerdoku achten muss wird man doch nie 
fertig. Zudem und das ist der Punkt, ich dieses Verhalten wie hier 
gezeigt niemals erwartet hätte. Wäre ich im Leben nie darauf gekommen.

Eigentlich müßte man den Compiler Leuten auf die Füße treten bis sich 
alle Werte nur innerhalb ihres Wertebereiches bewegen.

Edit:
Danke an A.K. für den Hinweis auf die Optionen

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

das wurde doch jetzt schon mehrfach geschrieben und verlinkt:
unsigned ist immer 2^n und damit ein Überlauf damit eindeutig.
signed kann 1er oder 2er Komplement sein oder beim Überlauf eine 
Exception auslösen wenn die HW so gebaut wurde. Da wäre das Verhalten HW 
Abhänging, aber die Sprache soll unabhängig sein und deshalb gilt die 
Vorgabe für signed das wenn a>b auch (a+1)>b ist. Für diejenigen die 
trotzdem HW abhängig sein wollen wurde der Compilerschalter eingebaut 
der die Optimierungen für signed sein lässt.

von Oliver S. (oliverso)


Lesenswert?

Veit D. schrieb:
> Dazu gleich noch eine Frage. Habt ihr sämtliche Compilerdoku im Kopf?
> Ich bin froh wenn ich Programmierbücher verstanden habe. Wenn ich dann
> noch in Abhängigkeit auf die Compilerdoku achten muss wird man doch nie
> fertig.

Nochmal im Klartext: das signed overflow UB ist,  ist nicht 
compilerabhängig, und steht auch nicht in dessen Doku. Das steht im 
Sprachstandard, und als Programmierer sollte man den kennen, und 
verstehen, daß jeder Compiler im Falle von UB tatsächlich unerwartete 
Dinge tun darf.

Veit D. schrieb:
> Würde bedeuten man kann bspw. kein Lauflicht mit signed char bauen.

Nur für den Fall, daß dir dafür ein Shift-Operator vorschwebt, vorsicht, 
(gleiche) Falle.

Es nutzt alles nix, ohne C(++)zu können, kann man kein C(++).

Wobei die signed - UB - Thematik immer noch intuitiver ist, als das 
letztens hier diskutiert UB im Falle einer Endlosschleife.

Oliver

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:

> Aber ob es jemals zum Einsatz kommt?

Man kann das z.B. nur für Release-Versionen einschalten, genau wie man 
strict-aliasing abschalten kann, sofern die Performance nicht merklich 
leidet. Ich mache sowas als reine Vorsichtsmaßnahme. Debug- und 
Testbuilds mit voller Optimierung, damit ich eventuelle Fehler kriege 
und den Source fixen kann.

von (prx) A. K. (prx)


Lesenswert?

Arduino Fanboy D. schrieb:
> Aber ob es jemals zum Einsatz kommt?

Von c-hater, wenn er gezwungen ist, die verhassten Sprachen zu nutzen?

von (prx) A. K. (prx)


Lesenswert?

Nop schrieb:
>> Aber ob es jemals zum Einsatz kommt?
>
> Man kann das z.B. nur für Release-Versionen einschalten,

Und -ftrapv könnte Debug-Versionen auf die Sprünge helfen. Scheint 
freilich auf Laufzeit-Funktionen rauszulaufen.

von Yalu X. (yalu) (Moderator)


Lesenswert?


von Oliver S. (oliverso)


Lesenswert?

Ich habe jetzt mal versucht, das verhalten auf dem PC (gcc 10.1.0, 
mingw, Windows) nachzuvollziehen. Die UB-Optimierung habe ich dort nicht 
hinbekommen.

Das einzige, was mir gelungen ist, ist eine Warnung in folgendem 
Programm:
1
#include <iostream>
2
#include <limits>
3
4
void print(int result)
5
{
6
  if (result < 0)
7
    std::cout << "negative " << result << std::endl;
8
  else
9
    std::cout << "positive " << result << std::endl;
10
}
11
12
int main()
13
{
14
  int result = 0;
15
  uint8_t add = 0xff;
16
  int numIterations = std::numeric_limits<int>::max()/add;
17
  std::cout << numIterations << std::endl;
18
19
  for (int i=0; i<numIterations+1; i++)
20
  {
21
    result += add;
22
23
    // uncomment for infinite loop
24
//    if (i > numIterations-5)
25
//      std::cout << result << std::endl;
26
  }
27
  std::cout << result << std::endl;
28
  result += add;
29
30
  print(result);
31
}

..\main.cpp:21:10: warning: iteration 8421504 invokes undefined behavior 
[-Waggressive-loop-optimizations]

output ist aber trotzdem:

8421504
-2147483521
negative -2147483266

-ftrapv schlägt dabei nicht zu, das reagiert wohl nur, wenn beide 
Operanden der Addition signed sind.

Das da irgendwo UB am Werk ist, merkt man, wenn man in der Schleife die 
Werte ausgeben will. Die wird dann zur Endlosschleife.

In print() ist aber immer der Test drin, da wird kein Zweig 
wegoptimiert.

avr-gcc dagegen optimiert in einem ähnlichen Programm immer einen der 
beiden Zweige in print() weg, allerdings auch mal den positiven, wenn 
der Compiler das Ergebnis berechnen kann.

Alles sehr seltsam.

Oliver

von Kaj (Gast)


Lesenswert?


von Yalu X. (yalu) (Moderator)


Lesenswert?

Oliver S. schrieb:
> In print() ist aber immer der Test drin, da wird kein Zweig
> wegoptimiert.

Mach print() static oder inline, damit es auch wirklich geinlinet
wird.

von Johannes (Gast)


Lesenswert?

Möglicherweise benutzt der Gcc für Avr andere Optionen, insb. in 
Zusammenhang mit den kürzeren Datentypen.

Auf die Schnelle ist mir das aufgefallen:
1
-faggressive-loop-optimizations
2
3
    This option tells the loop optimizer to use language constraints to derive bounds for the number of iterations of a loop. This assumes that loop code does not invoke undefined behavior by for example causing signed integer overflows or out-of-bound array accesses. The bounds for the number of iterations of a loop are used to guide loop unrolling and peeling and loop exit test optimizations. This option is enabled by default.

Ist ein etwas anderes Thema, aber hier wird mal explizit erwähnt, dass 
eine Optimierung über signed overflow stolpern könnte.

Man könnte sich jetzt mal anschauen was der avr gcc bei -O3 alles 
einschaltet. Möglicherweise werden intern auch andere Optionen gesetzt, 
kenne die Softwarearchitektur vom gcc nicht.
Bei Avr spielt die Codegröße jedenfalls eine wichtigere Rolle als bei 
x86, wäre naheliegend, dass da etwas mehr zugelassen wurde.

von Oliver S. (oliverso)


Lesenswert?

Yalu X. schrieb:
> Mach print() static oder inline, damit es auch wirklich geinlinet
> wird.

Ok, static inline hilft. Damit kommt es zum (un-)erwarteten output:

8421504
-2147483521
positive -2147483266

Wenn der Compiler den Wert vorberechnen kann, dann macht der ohne 
Warnung den overflow, und wirft dann den positive-Zweig raus.

Die UB-Behandlung haääte da dann doch etwas konsistenter gemacht werden 
können. So kann man sich ja gar nicht drauf verlassen ;)

Oliver

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Rainer V. schrieb:
> Ist der Programmcode falsch oder baut der Compiler wirklich Mist?

Der Programmcode ist meiner Meinung nach in Ordnung. Also baut der 
Compiler Mist.

Ob die Variable vor der Ausgabe undefiniert überläuft, oder nicht, 
spielt hier keine Rolle. Der darf daraus nicht einfach einen unsigned 
long machen. Das ist meine Meinung dazu.

Der Compiler mag sie Spezifikations-Konform verhalten, dann muss man 
halt den Schwarzen Peter an die Spezifikation weiter reichen.

Ich ich Software mit offensichtlichen Fehlfunktionen abliefere kann ich 
auch nicht auf die Spezifikation verweisen und sagen "das muss so sein".

von Oliver S. (oliverso)


Lesenswert?

Stefan ⛄ F. schrieb:
> Das ist meine Meinung dazu.

Es sollte doch inzwischen klar geworden sein, daß  der Compiler keinen 
Mist baut. Wenn etwas Mist ist, dann ist das der Sprachstandard. Ob das 
tatsächlich Mist ist, das wäre noch zu diskutieren.

Und nach aktuell gültigem C/C++-Sprachstandard ist der Programmcode 
nicht in Ordnung. Da helfen auch keine anderslautenden Meinungen.

Oliver

von Stefan F. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ich ich Software mit offensichtlichen Fehlfunktionen abliefere

Sollte heißen: Wenn ich Software mit offensichtlichen Fehlfunktionen 
abliefere

von (prx) A. K. (prx)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ob die Variable vor der Ausgabe undefiniert überläuft, oder nicht,
> spielt hier keine Rolle. Der darf daraus nicht einfach einen unsigned
> long machen.

Das macht er auch nicht von sich aus. Das hat der Programmierer von 
println in den Quellcode so reingeschrieben. Der Compiler hat aus den 
genannten Gründen lediglich den darin überflüssigen Code für die 
Behandlung negativer Werte entfernt.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

habe ein wenig nachgelesen, ihr macht das nicht umsonst für mich.  :-) 
Danke schon einmal. Ich kann hier lernen und Dinge mitnehmen.

Es gab scheinbar einmal die Option -fno-strict-overflow, die nur dazu 
diente das der Überlauf nicht wegoptimiert wird. Allerdings finde ich 
die nicht in der Optionsliste 
https://gcc.gnu.org/onlinedocs/gcc/Option-Index.html#Option-Index_op_letter-F
Es bliebe nur noch -fwrapv übrig wenn man das möchte und ich glaube 
derzeit genau das möchte ich.

Noch eine Frage zu dem UB Verhalten. Wenn man das im Code richtig 
behandeln möchte, kann man dann folgendes machen. Wir bleiben zur 
besseren Lesbarkeit bei char -128 ... +127.
1
if (++x > 127)  x = -128
Oder ist das UB Verhalten absolut nicht vorhersehbar? Also könnte er 
auch frei Schnauze x auf -50 setzen statt auf irgendeinen höheren 
positiven Wert und damit alles weiterhin durcheinander bringen? Wobei 
jeder normale Mensch sagen würde, bist du blöd, die Abfrage ist 
Schwachsinn, x kann niemals größer 127 werden. Eben weil man die Basics 
beherrscht.  :-) Allerdings habe ich gerade gelernt das der Compiler das 
intern anders machen kann.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Veit D. schrieb:
> habe ein wenig nachgelesen, ihr macht das nicht umsonst für mich.  :-)

Finde ich auch, in diesem Threads kann man einiges lernen.

von Veit D. (devil-elec)


Lesenswert?

A. K. schrieb:
> Stefan ⛄ F. schrieb:
>> Ob die Variable vor der Ausgabe undefiniert überläuft, oder nicht,
>> spielt hier keine Rolle. Der darf daraus nicht einfach einen unsigned
>> long machen.
>
> Das macht er auch nicht von sich aus. Das hat der Programmierer von
> println in den Quellcode so reingeschrieben. Der Compiler hat aus den
> genannten Gründen lediglich den darin überflüssigen Code für die
> Behandlung negativer Werte entfernt.

Diese Antwort ist aber nun wirklich falsch. Weil bis zur Print Methode 
blieb es int. Haben diverse Tests bewiesen. Der print Quellcode hat 
damit nichts zu tun. Nur das dann folgende optimieren führt/führte zur 
Verwirrung.

Das ist auch noch so ein Punkt den ich noch nicht voll verstehe. Wenn 
die Print Methode mit int Anwendung findet, muss da der Wert bis dahin 
noch korrekt negativ bzw. noch im int Wertebereich liegen. Ausgegeben 
wird jedoch ein unsigned long Wert. Nur wenn er vorher schon durch UB 
unsigned long wäre, dann würde die unsigned long print Methode 
verwendet, was aber nicht der Fall ist.

von Johannes S. (Gast)


Lesenswert?

int8_t x = 0;
if (++x > 127)  x = -128;

durchschaut der gcc:
[Warning] main.cpp@195,13: comparison is always false due to limited 
range of data type [-Wtype-limits]

und print(int, int) ruft print(long, int) auf, das ist so programmiert.

von Oliver S. (oliverso)


Lesenswert?

Der Compiler wird Dir bei ++x > 127 schon sagen, was er davon hält.

++x, x+1, und ähnliches laufen alle über, die kannst du da nicht 
verwenden. Du kannst nur vor der Addition auf x==127 prüfen, und dann 
statt der Addition x=128 hinschreiben.


Wenn du aber die Überlauf-Funktionalität so dringend brauchst, stellt 
sich die Frage, ob ein int8_t überhaupt der richtige Datentyp für die 
Anwendung ist. Nimm doch gleich einen uint8_t.

Oliver

: Bearbeitet durch User
von Johannes S. (Gast)


Lesenswert?

print(int, int):
https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Print.cpp#L77-L101

Also das hat sich der Compiler nicht selber ausgedacht.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Veit D. schrieb:
> Es gab scheinbar einmal die Option -fno-strict-overflow, die nur dazu
> diente das der Überlauf nicht wegoptimiert wird. Allerdings finde ich
> die nicht in der Optionsliste

Die Liste enthält die Optionen nur in ihrer positiven Form, also ohne
das "no". Hier geht es weiter:

  https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fstrict-overflow

von (prx) A. K. (prx)


Lesenswert?

Veit D. schrieb:
> habe ein wenig nachgelesen, ihr macht das nicht umsonst für mich.

Wohin dürfen wie die Rechnung schicken? ;-)

von Veit D. (devil-elec)


Lesenswert?

Danke euch.

> Wohin dürfen wie die Rechnung schicken? ;-)
An den Kurfürst August der Starke.    :-)

von Jemand (Gast)


Lesenswert?

Yalu X. schrieb:
> Auch die Konvertierung von negativen Werten in unsigned ist UB.

BULLSHIT
1
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting
2
one more than the maximum value that can be represented in the new type until the value is in the
3
range of the new type.

von (prx) A. K. (prx)


Lesenswert?

Veit D. schrieb:
> A. K. schrieb:
>> Das macht er auch nicht von sich aus. Das hat der Programmierer von
>> println in den Quellcode so reingeschrieben. Der Compiler hat aus den
>> genannten Gründen lediglich den darin überflüssigen Code für die
>> Behandlung negativer Werte entfernt.
>
> Diese Antwort ist aber nun wirklich falsch. Weil bis zur Print Methode
> blieb es int.

Bis zu welchem print?

Wenn man die zwischengeschalteten hierfür irrelevanten println=>print 
Funktionen ignoriert, gibt es eins für "int"
1
size_t Print::print(int n, int base)
2
{
3
  return print((long) n, base);
4
}
das eines für "long" aufruft
1
size_t Print::print(long n, int base)
2
{
3
  if (base == 0) {
4
    return write(n);
5
  } else if (base == 10) {
6
    if (n < 0) {
7
      int t = print('-');
8
      n = -n;
9
      return printNumber(n, 10) + t;
10
    }
11
    return printNumber(n, 10);
12
  } else {
13
    return printNumber(n, base);
14
  }
15
}

Im dortigen Aufruf von printNumber findet eine Umwandlung von "long" in 
"unsigned long" statt, denn so ist printNumber definiert:
1
size_t printNumber(unsigned long, uint8_t);

Hier sieht man den Teil des im Code-Listing integrierten Quellcodes von
1
size_t print(long, int = DEC);
der vom Compiler wegoptimiert wird, in der zulässigen Annahme, dass n 
nicht negativ sein kann:
1
 5cc:  c9 f7         brne  .-14       ; 0x5c0 <main+0x108>
2
    if (n < 0) {
3
      int t = print('-');
4
      n = -n;
5
      return printNumber(n, 10) + t;
6
    }
7
    return printNumber(n, 10);
8
 5ce:  07 2e         mov  r0, r23

Da der ganze Kram inlined ist, sieht der Compiler den Quellcode 
sämtlicher println/print Methoden und kann entsprechend handeln. Weshalb 
die Konvertierung nach "long" so weit verzögert wurde, dass sie im 
Code-Listing erst beim Aufruf von printNumber zu sehen ist.

> Haben diverse Tests bewiesen. Der print Quellcode hat
> damit nichts zu tun.

Natürlich hat er das. Weil inlined.
https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/Print.cpp#L77-L101

> Das ist auch noch so ein Punkt den ich noch nicht voll verstehe.

Wenn du erklärtermassen Dinge nicht verstehst, dann ist das ok. Aber hau 
das anderen, die es verstehen, nicht als Irrtum um die Ohren.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

ohne Irrtümer gebe es keine Weiterentwicklung. Entschuldigung für meine 
Dummheit. Ich danke dir dennoch das du mir meinen Denkfehler aufgezeigt 
hast.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Jemand schrieb:
> Yalu X. schrieb:
>> Auch die Konvertierung von negativen Werten in unsigned ist UB.
>
> BULLSHIT

Ganz schön große Brocken, mit denen du da um dich schmeißt :)

Ich meinte natürlich andersherum: Konvertierung von unsigned in signed,
wenn der Wert im signed-Typ nicht darstellbar ist. Aber auch das ist
nicht undefined, sondern implementation defined (und demnächst m.W.
sogar eindeutig spezifiziert).

Ok, nächstes mal werde ich vorher noch einmal nachlesen :)

von Rainer V. (a_zip)


Lesenswert?

Stefan ⛄ F. schrieb:
> Rainer V. schrieb:
>> Ist der Programmcode falsch oder baut der Compiler wirklich Mist?
>
> Der Programmcode ist meiner Meinung nach in Ordnung. Also baut der
> Compiler Mist.

Na, dass verwirrt mich jetzt doch wieder. Wenn man us-int mit s-int 
vergleicht, dann ist doch das "nirgendwo"formal richtig. Wenn ich in 
Assembler ein Byte-Register hochzähle und dann mit einem anderen 
Register, dass ich als s-int definiert habe, vergleiche, dann muß ich 
entscheiden, wie ich das Ergebnis interpretiere. Und genau diese 
Entscheidung führt nach meinem Verständnis zu dem Compiler-Desaster. 
Deshalb ist für mich der Programm-Code fehlerhaft.
Gruß Rainer

von Alex (Gast)


Lesenswert?

c-hater schrieb:
> ich komme gewöhnlich zum Einsatz, wenn die C/C++-Frickler am Ende mit
> ihrem Latein sind...

Scheint, als würde C/C++ dir dein Gehalt sichern :)

Gruß,

von S. R. (svenska)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ich ich Software mit offensichtlichen Fehlfunktionen
> abliefere kann ich auch nicht auf die Spezifikation
> verweisen und sagen "das muss so sein".

Wenn im Lastenheft steht, dass bei Drehzahl exakt 4000 ein Kurzschluss 
auftreten soll, obwohl du garnicht so genau messen kannst, dann musst du 
das trotzdem so abliefern.

Das explodiert dann nicht zuverlässig, ist aber nach Spezifikation. Es 
ist deine Aufgabe, auf unsinnige Spezifikationen hinzuweisen, aber 
nicht, sie wegen Unsinnigkeit zu unterlaufen.

von Joachim B. (jar)


Lesenswert?


von Einer K. (Gast)


Lesenswert?

Joachim B. schrieb:
> Arduino Fanboy D. schrieb:
>> Hi ....
>
> kannst du mal schauen?
> Beitrag "arduino oder GCC Programmierung"

Schon passiert...

von Joachim B. (jar)


Lesenswert?

Arduino Fanboy D. schrieb:
> Schon passiert...

danke! (mit Nebenwirkungen)

von Stefan F. (Gast)


Lesenswert?

S. R. schrieb:
> Es ist deine Aufgabe, auf unsinnige Spezifikationen hinzuweisen, aber
> nicht, sie wegen Unsinnigkeit zu unterlaufen.

Ich hätte den Senior Titel nicht bekommen, wenn ich es mir so einfach 
machen würde. Ich darf und soll mein Gehirn benutzen. Fließbandarbeiter 
haben wir genug in Osteuropa.

von MaWin O. (mawin_original)


Lesenswert?

Stefan ⛄ F. schrieb:
> Ich darf und soll mein Gehirn benutzen.

Dann nutze es doch mal und sieh ein, dass du den C-Standard so hinnehmen 
musst, wie er ist.
Ich verstehe nicht, warum hier wieder von vorne diskutiert wird.
Das Problem ist sehr eindeutig identifiziert.
Es ist ein fehlerhaftes Programm.

von Joachim B. (jar)


Lesenswert?

MaWin O. schrieb:
> Es ist ein fehlerhaftes Programm.

was für eine schlaue Antwort, es gibt fehlerhafte Programme überall, mal 
selbst kreiert weil man es nicht besser wusste, man von 
Compilerprogrammierer Profis deren Compiler man nur benutzt.
Sogar die Adafriut Profis die ihre Hardware verkaufen wollen machen 
manchmal C&P und rufen Funktionen auf die hinter der aufrufenden 
Funktion sind ohne eingebundenen Header da noch nicht mal bekannt sind!

Also ohne Nennung welches Programm den Fehler hat ist es nur 
Geschwurbel!

Ist ja nicht so als wenn es fehlerfreie SW überhaupt gibt!

Immer stellt sich irgendwann ein Fehler irgendwo raus, sonst bräuchte 
man im Leben keine Updates.

von Einer K. (Gast)


Lesenswert?

Joachim B. schrieb:
> Geschwurbel!

Der Fehler an dem Programm ist, dass die Möglichkeit besteht, dass ein 
int Überlauf stattfinden kann.

Wenn er dann wirklich stattfindet, dann zeigt sich der Fehler auch in 
aller Deutlichkeit.
Aus der Sicht, ist es sogar ein "schöner" Fehler.

Da hilft dein ganzes Geschwurbel keinen Funken weiter...
Will man C oder C++ nutzen, dann hat man sich der Gegebenheit zu beugen.

Allgemeiner:
Hat man einen Fehler im Programm, besteht die berechtigte "Hoffnung", 
dass sich dieser Fehler auch irgendwann zeigt.
So hier geschehen.

von Johannes S. (Gast)


Lesenswert?

Joachim B. schrieb:
> Sogar die Adafriut Profis die ihre Hardware verkaufen wollen machen
> manchmal C&P und rufen Funktionen auf die hinter der aufrufenden
> Funktion sind ohne eingebundenen Header da noch nicht mal bekannt sind!

Wieder ein Pluspunkt für C++, da ist das nämlich ein Fehler und der 
Compiler haut dir das um die Ohren.

von Oliver S. (oliverso)


Lesenswert?

Johannes S. schrieb:
> Wieder ein Pluspunkt für C++, da ist das nämlich ein Fehler und der
> Compiler haut dir das um die Ohren.

Wen man nicht mutwillig den Sparachstandard auf K&R setzt, ist das sogar 
einem C-Compiler eine Warnung wert.

Oliver

von Johannes S. (Gast)


Lesenswert?

K&R kannten schon C++? Glaube ich nicht, und in C++ ist eine Funktion 
ohne prototype einfach nicht erlaubt. Es kann ja die gleiche Funktion 
mit verschiedenen Signaturen geben. Das C dann einfach default Typen 
annimmt ist einfach gruselig. Man kann umgekehrt dem gcc sagen das auch 
in C sowas als Fehler gemeldet werden soll, wenn es nicht sogar default 
ist.
Oder war es so gemeint das auch C warnt? ok. Aber Warnungen schaltet die 
Arduino IDE per default aus um den Benutzer nicht zu sehr zu 
verwirren...

von Einer K. (Gast)


Lesenswert?

Johannes S. schrieb:
> Arduino IDE per default aus um den Benutzer nicht zu sehr zu
> verwirren...
Da sollte man sich aber dann nicht drauf ausruhen!

Außerdem baut der Builder die notwendigen Prototypen (in *.ino Dateien) 
auch gerne selber.
(meistens klappt das auch)

von (prx) A. K. (prx)


Lesenswert?

Johannes S. schrieb:
> in C++ ist eine Funktion ohne prototype einfach nicht erlaubt.

Ohne Prototype ist in C++ syntaktisch nicht möglich.

> Das C dann einfach default Typen annimmt ist einfach gruselig.

Schnee vor vorvorgestern. Interessiert heute nicht mehr. Damals 
verwendet man ggf. das Programm "lint", um die Parameterübergabe zu 
überprüfen. Die Compiler mussten in den 64 kB Adressraum einer PDP-11 
passen. Das konnte trotz eines in 2 Passes aufgeteiltem Compiler immer 
noch eine üble Overlay-Orgie sein.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

MaWin O. schrieb:
> Das Problem ist sehr eindeutig identifiziert.
> Es ist ein fehlerhaftes Programm.

Arduino Fanboy D. schrieb:
> Da hilft dein ganzes Geschwurbel keinen Funken weiter...

nicht verstanden wen ich zitierte?

Auch ein Compiler ist ein Programm und nicht immer fehlerfrei oder 
durchsichtig!

Muss man beim selber programmieren alle Compilerbesonderheiten kennen?

Wenn dem so ist muss man immer jeden µC oder SoC immer nur selbst bare 
metall programmieren, aber auch da ist man nicht gefeit vor Fehlern oder 
Irrtümern.
Selbst micro code auf CPU ist fehlerhaft oder kann fehlerhaft sein, ich 
sag bloß pentium bug!

also lassen wir es doch gleich ganz sein?
Wollt ihr mich eigentlich immer nur mißverstehen oder steckt da Methode 
hinter?

Johannes S. schrieb:
> Aber Warnungen schaltet die
> Arduino IDE per default aus um den Benutzer nicht zu sehr zu
> verwirren...

: Bearbeitet durch User
von Einer K. (Gast)


Lesenswert?

Natürlich darfst du hier alle Probleme dieser Welt aufzählen.
Bringt nur nix....

Oder anders:
Was möchtest du damit zum Ausdruck bringen?

Joachim B. schrieb:
> Auch ein Compiler ist ein Programm und nicht immer fehlerfrei oder
> durchsichtig!
In diesem Fall jedoch ohne jede Schuld.

Joachim B. schrieb:
> Muss man beim selber programmieren alle Compilerbesonderheiten kennen?
Der Programmierer muss sich an den C++ Sprachstandard halten.

Tut er das nicht, fällt er auf die Fresse!
So, es mir hier ergangen ist.

von c-hater (Gast)


Lesenswert?

Peter D. schrieb:

> Letztendlich stellt sich die Frage, gibt es irgendeine praktische
> Anwendung, wo ein Überlauf von 127 auf -128 sinnvoll ist?

Verallgemeinert wolltest du wohl fragen, ob es eine praktische Anwendung 
gibt, bei der ein signed integer Überlauf einen Sinn ergibt (unabhängig 
von der Bitbreite).

Und die Antwort ist ganz klar: unzählige. Alles, was mit zyklischen 
Zahlenräumen hantiert. Also insbesondere alles aus dem Bereich 
Kryptographie. Es gibt aber noch sehr viel mehr Anwendungen.

von Joachim B. (jar)


Lesenswert?

Arduino Fanboy D. schrieb:
> So, es mir hier ergangen ist.

nicht nur dir!

Ich denke das passiert jedem mal, ich wundere mich immer bei str 
Funktionen über signed char oder unsigned char, für mich ist ein char 
immer unsigned und manchmal sind Char auch 2 Byte!

Ich habe mir angewöhnt sparsam zu programmieren also wenn ich nur 0-255 
als Var brauche uint8_t, aber manchmal kann int oder unsigned int 
nützlicher sein, wer RAM genug hat.

von Einer K. (Gast)


Lesenswert?

Joachim B. schrieb:
> ich wundere mich immer bei str
> Funktionen über signed char oder unsigned char, für mich ist ein char
> immer unsigned
Du wirst dich solange wundern, bis du mal nachgelesen hast, wie C oder 
C++ den Type char definieren.

Joachim B. schrieb:
> und manchmal sind Char auch 2 Byte!
Ach nee....
Und das Byte hat dann irgendwas zwischen 5 und 36 Bit.
Oder?

Tipp:
sizeof(char) liefert immer 1
IMMER
Egal auf welchem Bügelbrett der Code läuft.

von Oliver S. (oliverso)


Lesenswert?

Arduino Fanboy D. schrieb:
> Tipp:
> sizeof(char) liefert immer 1

Tipp: Standard lesen. sizeof(char) liefert zwar immer 1, das aber 
bedeutet nicht das, was du meinst. Denn, wie schon geschrieben wurde, 
kann ein char auch mehrer Byte groß sein.

Oliver

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> für mich ist ein char immer unsigned

Keineswegs.

Ein char ist ein char ist etwas, in dem man Zeichen vorhält, die man an 
entsprechende Funktionen weitergibt, die char verarbeiten. Da man mit 
einem solche char nie in die Verlegenheit kommt zu rechnen oder Bits zu 
schieben, kann es einem portablen Programm egal sein, ob der Compiler 
das nun signed oder unsigned implementiert. (GCC macht aus hysterischen 
Gründen standardmäßig signed.)

Wenn man mit kleinen Zahlen rechnen möchte, schreibt man explizit signed 
char oder unsigned char – oder besser, man nimmt <stdint.h> und daraus 
uint8_t bzw. int8_t.

Dass sizeof(char) in C garantiert gleich 1 ist, wurde dir ja auch 
gerade erklärt. (Das bedeutet allerdings nicht, dass CHAR_BIT deshalb 
zwangsläufig immer 8 wäre, auch wenn das bei den allermeisten Maschinen 
sicher so ist.)

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> kann ein char auch mehrer Byte groß sein

Nein.
1
3.6
2
byte
3
addressable unit of data storage large enough to hold any member of the
4
basic character set of the execution environment.
5
6
NOTE 1
7
It is possible to express the address of each individual byte of an
8
object uniquely.
9
NOTE 2 A byte is composed of a contiguous sequence of bits, the number
10
of which is implementation-defined. The least significant bit is called
11
the low-order bit; the most significant bit is called the high-order bit.

von MaWin O. (mawin_original)


Lesenswert?

Oliver S. schrieb:
> Denn, wie schon geschrieben wurde,
> kann ein char auch mehrer Byte groß sein.

Nö. Falsch.
Meinst du vielleicht, dass ein Byte nicht immer 8 Bit haben muss?

von (prx) A. K. (prx)


Lesenswert?

Oliver S. schrieb:
> Tipp: Standard lesen. sizeof(char) liefert zwar immer 1, das aber
> bedeutet nicht das, was du meinst. Denn, wie schon geschrieben wurde,
> kann ein char auch mehrer Byte groß sein.

"A char whether signed or unsigned, occupies exactly one byte."

von Einer K. (Gast)


Lesenswert?

Oliver S. schrieb:
> das aber bedeutet nicht das, was du meinst.
Da irrst du dich aber ganz schwer!

von Stefan F. (Gast)


Lesenswert?

Ihr verweist hier immer schön auf "Standard lesen". Aber wo kann ich 
denn als normal-sterblicher den Standard nachlesen?

Die offiziellen Dokumente kosten offenbar eine dicken Batzen Geld.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Stefan ⛄ F. schrieb:
> Ihr verweist hier immer schön auf "Standard lesen". Aber wo kann ich
> denn als normal-sterblicher den Standard nachlesen?

http://www.open-std.org/jtc1/sc22/wg14/www/documents

N1124 beispielsweise ist der de-facto Standard C99 mit den beiden 
eingearbeiteten technischen Korrigenda TC1 und TC2. Er nennt sich 
natürlich "Draft", weil eben die offiziellen Standarddokumente nur gegen 
Geld zu haben sind.

N2478/N2479 dürften die aktuellen Drafts des nächsten Standards (derzeit 
noch "C2x" tituliert) sein.

N1570 ist der final draft von ISO9899:2011, dazu gibt's noch ein TC mit 
Nummer N1606.

Das Dokument für C18 finde ich allerdings gerade nicht.

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


Lesenswert?

Stefan ⛄ F. schrieb:
> Die offiziellen Dokumente kosten offenbar eine dicken Batzen Geld.

Es ist (oder war früher) üblicherweise kein Problem, Entwürfe der 
Standards im Netz aufzutreiben.

Lohnend ist speziell bei C99 auch das erklärende Begleitdokument 
C99RationaleV5.10.pdf. Aus dem stammt der vorhin gepostete klärende Satz 
zu char=byte, während es bei den Standards zwar auch irgendwie draus 
hervorgeht, aber nirgends in solcher Klarheit.

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


Lesenswert?


von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?


von Einer K. (Gast)


Lesenswert?

Mir ist dieses recht sympatisch:
https://en.cppreference.com/w/
Ist allerdings mehr so eine Art Benutzerhandbuch.

von Stefan F. (Gast)


Lesenswert?

Das C99 Dokument habe ich auch gefunden, aber wir arbeiten dich längst 
mit C11 (oder gar neuer). Gerade bei solchen internen Details, wie das 
gerade diskutierte, gehe ich davon aus, dass sie nicht ewig unverändert 
bleiben.

von Oliver S. (oliverso)


Lesenswert?


von Peter D. (peda)


Lesenswert?

Stefan ⛄ F. schrieb:
> Gerade bei solchen internen Details, wie das
> gerade diskutierte, gehe ich davon aus, dass sie nicht ewig unverändert
> bleiben.

Ich habe eher den Eindruck, daß einmal getroffene Vereinbarungen in 
Stein gemeißelt sind, d.h. sie bleiben für ewig.
Z.B. fallen viele regelmäßig darauf herein, daß char signed ist und 
wundern sich, warum Umlaute (>127) Probleme machen. Viele Funktionen der 
string.h. casten daher intern nach unsigned char.

von (prx) A. K. (prx)


Lesenswert?

Stefan ⛄ F. schrieb:
> Gerade bei solchen internen Details, wie das
> gerade diskutierte, gehe ich davon aus, dass sie nicht ewig unverändert
> bleiben.

Ich wiederum gehe davon aus, dass sich daran nichts ändern wird. In 
diesem Fall geht es zwar nicht um bestehendes Verhalten, sondern um 
undefiniertes, weshalb man keine heilige Kuh schlachten würde. 
Allerdings sehe dann zwei Probleme: Die Migration von einem Compiler 
nach neuem Standard auf einen nach altem Standard wäre problematisch und 
mancher bestehende Code würde langsamer.

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Peter D. schrieb:
> Z.B. fallen viele regelmäßig darauf herein, daß char signed ist

Da kann der Standard aber nichts dafür, der überlässt das der 
Implementierung, ob ein char signed oder unsigned ist.

Oliver

von (prx) A. K. (prx)


Lesenswert?

Oliver S. schrieb:
>> Z.B. fallen viele regelmäßig darauf herein, daß char signed ist
>
> Da kann der Standard aber nichts dafür, der überlässt das der
> Implementierung, ob ein char signed oder unsigned ist.

Die Standards können sehr wohl etwas dafür, eben gerade weil sie das 
Vorzeichen von char bewusst nicht definieren. Es ging um die Frage, ob 
ein zukünftiger Standard das Überlaufverhalten klar definieren wird, 
oder sollte.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Viele Funktionen der string.h. casten daher intern nach unsigned char.

So? Welche tun das denn?
1
$ cd src/avr-libc
2
$ fgrep unsigned include/string.h 
3
    as an unsigned character) stops the operation.
4
    and s2. The comparision is performed using unsigned char operations.
5
    incorrect results if you use an unsigned char or char due to truncation.

Also einen Cast sehe ich da nicht. (Die gesamte Implementierung der 
entsprechende Funktionen ist übrigens mittlerweile Assemblercode.)

Die einzigen Funktionen, die in <string.h> irgendwas arithmetisches 
machen, sind die Vergleichsfunktionen. Für diese schreibt der Standard 
explizit vor, dass die zugrunde liegenden Bytes als unsigned char zu 
interpretieren sind (Kapitel 7.21.4). Das wirkt sich letztlich aber nur 
auf den Vergleich kleiner oder größer als aus: der Vergleich auf 
(Un-)Gleichheit funktioniert unabhängig von einer 
Vorzeicheninterpretation sowieso.

Ansonsten ist es überhaupt kein Problem, in einem char einen 
ISO-8859-1-Umlaut unterzubringen, egal ob der Compiler das nun als 
signed oder unsigned betrachtet: mit dem muss ja nicht gerechnet werden, 
der steht da einfach als Zeichen drin. Irgendwelche strcmp-Vergleiche 
auf kleiner oder größer haben bei Strings mit Sonderzeichen sowieso 
keinen Sinn, und für Gleichheit wiederum ist es auch egal, ob das 
default char nun ein Vorzeichen hat oder nicht. (Für sprachlich korrekte 
Vergleiche gibt es strcoll(), aber dafür muss man auch definieren, 
welchen Zeichensatz man benutzt.)

von (prx) A. K. (prx)


Lesenswert?

Jörg W. schrieb:
> So? Welche tun das denn?

Vielleicht meinte er ctype.h.

Kurzfassung:
https://searchcode.com/codesearch/view/10273916/
Langfassung:
https://code.woboq.org/qt5/include/ctype.h.html

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

A. K. schrieb:
> Vielleicht meinte er ctype.h.

Naja, die SDCC-Version ist irgendwas, aber nichts, was mit dem Standard 
konform ist: im Standard haben sowohl Argument als auch Rückkehrwert den 
Typ "int".

Des weiteren gehört natürlich islower() eindeutig zur Implementierung 
und muss daher nicht portabel geschrieben sein. Es darf daher bspw. die 
Annahme treffen, dass der execution character set stets nur ASCII sein 
kann (wirkliche locales scheinen ja beim SDCC nicht betrachtet zu 
werden, genauso wenig wie bei avr-libc), daher ist der Cast dort selbst 
dann überflüssig, wenn man das Argument korrekt als "int" deklariert.

Die andere Version enthält keinerlei Casts auf unsigned char.

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Die einzigen Funktionen, die in <string.h> irgendwas arithmetisches
> machen, sind die Vergleichsfunktionen.

Genau diese müssen unsigned char verwenden. D.h. beim Vergleich kein 
BRGE, BRLT für signed.

Jörg W. schrieb:
> Ansonsten ist es überhaupt kein Problem, in einem char einen
> ISO-8859-1-Umlaut unterzubringen

Z.B. bei der Konvertierung ASCII-Umlaute nach LCD-Umlaute:
1
  switch ((unsigned char) c)  // <-- Pitfall !!!
2
  {
3
    case 'ä': c = 0xE1; break;
4
// ...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:

> Genau diese müssen unsigned char verwenden.

Ja, weil es so auch im Standard steht.

Aber ich schrieb auch: für einen Vergleich auf (Un-)Gleichheit wäre 
selbst das nicht nötig. Es beschreibt lediglich das Verhalten, wann ein 
Ergebnis von -1 oder 1 zu erwarten ist. Portabler Code kann sich aber 
sowieso nicht drauf verlassen, dass bspw. 'a' kleiner als 'b' ist 
(wenngleich das sogar bei EBCDIC der Fall ist, dort sind die Buchstaben 
aber nicht lückenlos aneinander).

Lediglich die lückenlos aufsteigende Reihenfolge der Ziffern '0' bis '9' 
ist vom Standard gedeckt.

>> Ansonsten ist es überhaupt kein Problem, in einem char einen
>> ISO-8859-1-Umlaut unterzubringen
>
> Z.B. bei der Konvertierung ASCII-Umlaute nach LCD-Umlaute:

"ASCII-Umlaute" ist eine leere Menge. ;-)

>
1
>   switch ((unsigned char) c)  // <-- Pitfall !!!
2
>   {
3
>     case 'ä': c = 0xE1; break;
4
> // ...
5
>

Der Cast ist unsinnig. Ein case-Label testet auf Gleichheit, dafür 
genügt "char" vollständig als Typ, da muss man nichts casten.

Allerdings ist das alles sowieso schwierig, denn hier entstehen ggf. 
Diskrepanzen zwischen host character set und execution character set: 
wenn mein Quellcode UTF-8 benutzt (was mittlerweile praktisch 
ausschließlich der Fall ist), die Zielarchitektur aber nicht, dann steht 
da im case-Label ein Multibyte-Zeichen. Daher habe ich mir für diese 
Fälle angewöhnt, lieber gleich Ersetzungsstrings zu benuzten:
1
#define ae "\xe1" // LCD character 'ä'
2
3
...
4
   lcd_print("Es wird bem"ae"ngelt");

von Stefan F. (Gast)


Lesenswert?

A. K. schrieb:
> Die Standards können sehr wohl etwas dafür, eben gerade weil sie das
> Vorzeichen von char bewusst nicht definieren.

Bei Java finde ich sehr angenehm, dass alle Datentypen eine eindeutig 
definierte Größe haben. Ganz unabhängig von der Hardware-Plattform 
darunter. Darunter leidet natürlich die Performance ein bisschen, aber 
das stand bei Java vermutlich viel weiter unten in der Liste der 
Prioritäten.

von (prx) A. K. (prx)


Lesenswert?

Stefan ⛄ F. schrieb:
> Bei Java finde ich sehr angenehm, dass alle Datentypen eine eindeutig
> definierte Größe haben.

Die Erfahrungen mit C sind darin natürlich eingeflossen. Es kam sehr 
viel später und zwischenzeitlich hatten sich Zweierkomplement und 
8/16/32/64-Bit Architekturen durchgesetzt.

von Johannes S. (Gast)


Lesenswert?

und in Java darf ein signed überlaufen :)

von Oliver S. (oliverso)


Lesenswert?

Stefan ⛄ F. schrieb:
> aber
> das stand bei Java vermutlich viel weiter unten in der Liste der
> Prioritäten.

So ist es.

Für die Anwendungen, wo es ohne sich „unnormal anfühlende“ Dateitypen 
nicht geht, ist es aber gut, daß es C gibt.

Oliver

von köasdkjöfa (Gast)


Lesenswert?

Veit D. schrieb:
> Ansonsten muss man die Frage stellen dürfen warum mit unsigned
> Datentypen der Wert bei Überlauf auf 0 zurückfällt? Kann mathematisch
> nicht korrekt sein. Kleiner werden geht mathematisch nicht.

Doch, natürlich. Das ist Modulo Arithmetik.

von Oliver S. (oliverso)


Lesenswert?

Das ist schlicht binäre Arithmetik. Beim Überlauf wird halt das n+1 Bit 
gesetzt (was es nicht gibt, oder nur als overflow-Flag). Ein 
Binäraddierer macht das automatisch so, der kann gar nicht anders.

Oliver

von (prx) A. K. (prx)


Lesenswert?

Oliver S. schrieb:
> Das ist schlicht binäre Arithmetik. Beim Überlauf wird halt das n+1 Bit
> gesetzt (was es nicht gibt, oder nur als overflow-Flag). Ein
> Binäraddierer macht das automatisch so, der kann gar nicht anders.

Das betrifft zwar nicht "unsigned" sondern "signed", aber ein 
Binäraddierer für Darstellung im Einerkomplement kann sehr wohl anders. 
Denn bei dem landet der oben rausfallende Übertrag nicht im Nirgendwo, 
sondern wird unten wieder reingeführt (end-around carry).

Weshalb man auf solcher Hardware entweder separate Befehle für signed 
und unsigned benötigt, oder der Modulo-Effekt von unsigned ist im Eimer.

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


Lesenswert?

Peter D. schrieb:
> switch ((unsigned char) c)  // <-- Pitfall !!!
>   {
>     case 'ä': c = 0xE1; break;
> // ...

Dabei kommt es stark darauf an, in welchem Character-Set Dein Quellcode 
vorliegt. Portabler wäre auch hier, statt 'ä' einfach den gemeinten 
Hex-Wert links vom Doppelpunkt zu verwenden.

von Stefan F. (Gast)


Lesenswert?

Frank M. schrieb:
> Portabler wäre auch hier, statt 'ä' einfach den gemeinten
> Hex-Wert links vom Doppelpunkt zu verwenden.

So mache ich das auch immer. Ist viel einfacher, als mit irgendwelchen 
Zeichensatz-Settings zu hantieren.

von Peter D. (peda)


Lesenswert?

Frank M. schrieb:
> Portabler wäre auch hier, statt 'ä' einfach den gemeinten
> Hex-Wert links vom Doppelpunkt zu verwenden.

Ich habe nochmal in den Code geschaut, da verwende ich nicht 'ä' sondern 
auch den Hexwert.
Wenn man dann den cast nach unsigned char vergißt, gibt es ne Warnung:
"warning: case label value exceeds maximum value for type"
Für die Zuweisung Wert>127 nach char interessiert ihn das nicht mehr, 
die erfolgt korrekt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> "warning: case label value exceeds maximum value for type"

Dann schreib den case label doch als negative Zahl hin. ;-)

von Peter D. (peda)


Lesenswert?

Jörg W. schrieb:
> Dann schreib den case label doch als negative Zahl hin. ;-)

Geht natürlich auch:
1
  switch (c) 
2
  {
3
    case (signed char)132:

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> switch (c)
>   {
>     case (signed char)132:

Oder gleich:
1
  switch (c)
2
  {
3
     case -124: // ...

Löst eben nur nicht die Diskrepanz, dass der host character set 
heutzutage oftmals nicht mehr in einem Byte abbildbar ist, ein 'ä' damit 
nicht mehr in 'char' passt. Nun kann man da mit allen möglichen 
Kommandozeilenoptionen herumoperern, oder man findet eben eine 
pragmatische Lösung wie von mir oben vorgeschlagen, und dann kann man 
auch gleich den passenden LCD-Code da benutzen. So eine
1
print("Ver"ae"rgerung");

lässt sich doch nun wirklich nur wenig schwieriger optisch erfassen als 
mit dem echten Umlaut, und man muss zur Laufzeit gar nichts mehr 
rechnen.

Meinen Editor auf irgendeine Einstellung aus dem letzten Jahrtausend 
zurücksetzen¹) würde ich deshalb jedenfalls nicht wollen.

¹) bpsw. ISO-8859-1

von Stefan F. (Gast)


Lesenswert?

Jörg W. schrieb:
> print("Ver"ae"rgerung");

Interessante Idee.

von Joachim B. (jar)


Lesenswert?

Jörg W. schrieb:
> So eine
> print("Ver"ae"rgerung");
> lässt sich doch nun wirklich nur wenig schwieriger optisch erfassen als
> mit dem echten Umlaut,

interessante Variante.......

ich notiere mir ja im Source:
// 309908 Bytes für lokale Variablen verbleiben.
und dann passiert folgendes beim Neuladen:

// 13774 Bytes f ’¢Â......... geht "unendlich" weiter

erst das f von für und irgendwann mal das r von für oder nie!
1
// 13774 Bytes f ’¢¢â€šÂ¬¢â€žÂ¢’Æ‬™’¢¢â€šÂ¬‚ ’Æâ€â€šÃ‚¢’¢¢â‚¬Å¡‚¬’¢¢â‚¬Å¾‚¢’Æ‬™’†¢â‚¬â„¢’Æâ€Â¢Ã¢â€šÂ¬Ã…¡’â₂¢’Æ‬™’â₂¢’Æâ€â€šÃ‚¢’¢¢â‚¬Å¡‚¬’…‚¡’Æâ€Â¢Ã¢â€šÂ¬Ã…¡’â₂¬’Æ‬™’¢¢â€šÂ¬…¡’Æâ€Â¢Ã¢â€šÂ¬Ã…¡’â₂ ’Æ‬™’†¢â‚¬â„¢’Æâ€Â¢Ã¢â€šÂ¬Ã‚ ’¢¢â€šÂÂ�

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> und dann passiert folgendes beim Neuladen:

Dann hast du bzw. dein Editor halt noch ein größeres Problem … das 
habe ich nicht, aber das Problem hier hätte ich halt schon:
1
$ cat umlaut.c
2
#include <string.h>
3
#include <stdio.h>
4
5
int
6
main(int argc, char **argv)
7
{
8
  if (argc < 2)
9
    return 0;
10
11
  for (int i = 0; i < strlen(argv[1]); i++) {
12
    switch (argv[1][i]) {
13
    case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
14
    default: printf("Kein Umlaut.\n");
15
    }
16
  }
17
18
  return 0;
19
}
20
$ gcc -o umlaut -O umlaut.c
21
umlaut.c: In function 'main':
22
umlaut.c:12:10: warning: multi-character character constant [-Wmultichar]
23
     case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
24
          ^~~~
25
umlaut.c:12:5: warning: case label value exceeds maximum value for type
26
     case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
27
     ^~~~
28
umlaut.c:12:21: warning: multi-character character constant [-Wmultichar]
29
     case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
30
                     ^~~~
31
umlaut.c:12:16: warning: case label value exceeds maximum value for type
32
     case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
33
                ^~~~
34
umlaut.c:12:32: warning: multi-character character constant [-Wmultichar]
35
     case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
36
                                ^~~~
37
umlaut.c:12:27: warning: case label value exceeds maximum value for type
38
     case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
39
                           ^~~~
40
umlaut.c:12:38: warning: statement will never be executed [-Wswitch-unreachable]
41
     case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
42
                                      ^~~~~~~~~~~~~~~~~~~

Nun kann man das zwar zurecht fummeln, bspw.:
1
$ gcc -o umlaut -fexec-charset=ISO-8859-1 -finput-charset=UTF-8 -O umlaut.c

Aber: mein Terminal macht ja trotzdem noch UTF-8, also:
1
$ ./umlaut Hällo
2
Kein Umlaut.
3
Kein Umlaut.
4
Kein Umlaut.
5
Kein Umlaut.
6
Kein Umlaut.
7
Kein Umlaut.

Es werden 6 Zeichen erkannt, denn ä sind zwei Byte, und der Umlaut wird 
nicht so erkannt.

Das ist das, was ich oben meinte, und weshalb ich dann lieber gleich 
einen Workaround für solche Dinge wie ein LCD nehme.

: Bearbeitet durch Moderator
von Rainer V. (a_zip)


Lesenswert?

Jörg W. schrieb:

> $ gcc -o umlaut -O umlaut.c
> umlaut.c: In function 'main':
> umlaut.c:12:10: warning: multi-character character constant
> [-Wmultichar]
>      case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
>           ^~~~
> umlaut.c:12:5: warning: case label value exceeds maximum value for type
>      case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
>      ^~~~
> umlaut.c:12:21: warning: multi-character character constant
> [-Wmultichar]
>      case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
>                      ^~~~
> umlaut.c:12:16: warning: case label value exceeds maximum value for type
>      case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
>                 ^~~~
> umlaut.c:12:32: warning: multi-character character constant
> [-Wmultichar]
>      case 'ä': case 'ö': case 'ü': printf("Umlaut!\n"); break;
>                                 ^~~~
>
> Aber: mein Terminal macht ja trotzdem noch UTF-8, also:
> $ ./umlaut Hällo
> Kein Umlaut.
> Kein Umlaut.
> Kein Umlaut.
> Kein Umlaut.
> Kein Umlaut.
> Kein Umlaut.


Also jetzt erst mal ehrlich...wann brauche ich auf einem Controller "Ä" 
oder "ü"...rein gar nicht!
Gruß Rainer
PS: sorry, mußte das Zitat kürzen...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Rainer V. schrieb:
> wann brauche ich auf einem Controller "Ä" oder "ü"

Wenn du dem, der vor dem daran angeschlossenen LCD sitzt, "Mensch, 
ärgere dich nicht!" mitteilen möchtest statt "Mensch, aergere dich 
nicht!".

Es soll ja Controller geben, die nicht nur für den C-Programmierer 
selbst da sind.

: Bearbeitet durch Moderator
von Joachim B. (jar)


Lesenswert?

Rainer V. schrieb:
> Also jetzt erst mal ehrlich...wann brauche ich auf einem Controller "Ä"
> oder "ü"...rein gar nicht!
> Gruß Rainer

und da ich oft DHT oder die RTC nach Temperaturen befrage habe ich mir 
den Kuller von Grad C "°C" als Grafik Zeichensatz eingebaut, aber im 
Quelltext verbietet sich auch das "°C"

also muss ich im Source per #define GRAD einsetzen und das in der 
Serial.print Ausgabe extra behandeln, rauswerfen und °C ausgeben.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> also muss ich im Source per #define GRAD einsetzen und das in der
> Serial.print Ausgabe extra behandeln, rauswerfen und °C ausgeben.

Warum machst du das nicht so wie ich?
1
#define DEG "\x01" // oder wo auch immer dein Gradzeichen liegt
2
3
   double temp = get_temperature();
4
   lcd_printf("Temperatur: %4.1f "DEG"C", temp);

: Bearbeitet durch Moderator
von Philipp Klaus K. (pkk)


Lesenswert?

Jörg W. schrieb:
> A. K. schrieb:
>> Vielleicht meinte er ctype.h.
>
> Naja, die SDCC-Version ist irgendwas, aber nichts, was mit dem Standard
> konform ist: im Standard haben sowohl Argument als auch Rückkehrwert den
> Typ "int".

Ja, die frühen Versionen von SDDC hatten sich da noch zu sehr an Keil 
orientiert, aber seither hat sich bei der Standardkonformität von SDCC 
viel verbessert.

> Des weiteren gehört natürlich islower() eindeutig zur Implementierung
> und muss daher nicht portabel geschrieben sein. Es darf daher bspw. die
> Annahme treffen, dass der execution character set stets nur ASCII sein
> kann (wirkliche locales scheinen ja beim SDCC nicht betrachtet zu
> werden, genauso wenig wie bei avr-libc), daher ist der Cast dort selbst
> dann überflüssig, wenn man das Argument korrekt als "int" deklariert.

Aus der Perspektive des C-Standards hat SDCC nur den C locale, welcher 
dort UTF-8 ist. Andere Zeichensätze werden nur eingeschränkt 
unterstützt.

von Joachim B. (jar)


Lesenswert?

Jörg W. schrieb:
> Warum machst du das nicht so wie ich?
> #define DEG "\x01" // oder wo auch immer dein Gradzeichen liegt

gute Frage, ich habe viel probiert und bin nun fast dort gelandet.

Leider gelingt es mir nicht das gleich mit einem Zeichen in den Text zu 
bringen, also so

T= 27,5°C soll es ja sein in der Textausgabe per Serial und im Display.

Nun schreibe ich das Gradzeichen als GRAD direkt in die Ausgabezeile an 
Stelle 12.

Für das Display klappt das prima, aber als Textausgabe mal ja, mal nein

Ausgabe Arduino:
1
 --------------
2
|Di 21.Jul 2020|
3
|20:37:30 Uhr R|( min - max )
4
| DHT H= 36,9% |(31.1 - 49.7)
5
| DHT T= 25,2°C|(22.5 - 27.4)
6
| RTC T= 25,5°C|
7
|con=63 hell=08|
8
 --------------

Ausgabe HyperTerminal:
1
 --------------
2
|Di 21.Jul 2020|
3
|20:45:00 Uhr R|( min - max )
4
| DHT H= 36,8% |(36.7 - 37.2)
5
| DHT T= 25,2°C|(25.2 - 25.2)
6
| RTC T= 25,5°C|
7
|con=63 hell=08|
8
 --------------

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Joachim B. schrieb:
> Für das Display klappt das prima, aber als Textausgabe mal ja, mal nein

Das ist eben das, was der C-Standard den "execution character set" 
nennt. Der ist zwischen den beiden unterschiedlich.

Philipp Klaus K. schrieb:
> Aus der Perspektive des C-Standards hat SDCC nur den C locale, welcher
> dort UTF-8 ist.

Ist ja auch völlig in Ordnung (avr-libc bspw. unterstützt nur ASCII), 
aber dann muss man nicht so ein Gehampel mit irgendwelchen Typecasts 
machen, denn dann weiß man, dass a-z und A-Z sowieso alle in einem 
Bereich sind, wo sie selbst bei "signed char" noch positiv sind. Code 
der C-Bibliothek muss doch nicht den Anspruch haben, auf x-beliebige 
Compiler und Ausführungsumgebungen (hier: execution character set) 
portabel zu sein.

von Philipp Klaus K. (pkk)


Lesenswert?

> Philipp Klaus K. schrieb:
>> Aus der Perspektive des C-Standards hat SDCC nur den C locale, welcher
>> dort UTF-8 ist.
>
> Ist ja auch völlig in Ordnung (avr-libc bspw. unterstützt nur ASCII),
> aber dann muss man nicht so ein Gehampel mit irgendwelchen Typecasts
> machen, denn dann weiß man, dass a-z und A-Z sowieso alle in einem
> Bereich sind, wo sie selbst bei "signed char" noch positiv sind. Code
> der C-Bibliothek muss doch nicht den Anspruch haben, auf x-beliebige
> Compiler und Ausführungsumgebungen (hier: execution character set)
> portabel zu sein.

Der verlinkte Code ist 10 Jahre alt (aus einer Zeit, als SDCC nur ASCII 
unterstützte). Inzwischen sieht es in ctype.h so aus:

~~~~
inline int islower (int c)
{
  return ((unsigned char)c >= 'a' && (unsigned char)c <= 'z');
}
~~~~

Die Casts dienen dazu, dass beim Vergleich zwei unsigned char 
miteinander verglichen werden, was auf dem meisten 
SDCC-Zielarchitekturen deutlich effizienter als ein Vergleich zweier int 
ist.

vor 10 Jahren war dazu wohl noch mehr "Gehampel" notwendig, aber die 
damaligen Casts dürfte den gleichen Zweck gehabt haben.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Philipp Klaus K. schrieb:
> Die Casts dienen dazu, dass beim Vergleich zwei unsigned char
> miteinander verglichen werden, was auf dem meisten
> SDCC-Zielarchitekturen deutlich effizienter als ein Vergleich zweier int
> ist.

Das ist natürlich ein Argument. Andererseits sollte der Compiler das 
auch ganz selbstständig so umsetzen können, denn beim Vergleich mit den 
entsprechenden Konstanten ist klar, dass das Vorzeichen keine Rolle 
spielt. Aber gut möglich, dass die Optimierungsstrategien des SDCC nicht 
so ausgefeilt sind, ich kenne ihn nur dem Namen nach.

> vor 10 Jahren war dazu wohl noch mehr "Gehampel" notwendig

Mit "Gehampel" meinte ich vor allem diese seltsame Mimik, für einen 
simplen Cast auch noch einen Makro zu haben.

von Philipp Klaus K. (pkk)


Lesenswert?

Jörg W. schrieb:
> Philipp Klaus K. schrieb:
>> Die Casts dienen dazu, dass beim Vergleich zwei unsigned char
>> miteinander verglichen werden, was auf dem meisten
>> SDCC-Zielarchitekturen deutlich effizienter als ein Vergleich zweier int
>> ist.
>
> Das ist natürlich ein Argument. Andererseits sollte der Compiler das
> auch ganz selbstständig so umsetzen können, denn beim Vergleich mit den
> entsprechenden Konstanten ist klar, dass das Vorzeichen keine Rolle
> spielt. Aber gut möglich, dass die Optimierungsstrategien des SDCC nicht
> so ausgefeilt sind, ich kenne ihn nur dem Namen nach.

Es macht durchaus einen Unterschied im Ergebnis ob da der cast ist. Z.B. 
bei islower(-153). Somit kann SDCC diesen cast nicht einfach als 
Optimierung selbst einfügen.
SDCC weiß in dem Moment, in dem die Implementierung von islower 
kompiliert nicht, dass der Standard für islower undefiniertes Verhalten 
erlaubt, falls das Argument nicht in einen unsigned char passt.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Philipp Klaus K. schrieb:
> Somit kann SDCC diesen cast nicht einfach als Optimierung selbst
> einfügen.

Doch, kann er: negative Zahlen (bei signed char) kann er von vornherein 
alle als "islower" ausschließen, zumindest bei ASCII, denn sie liegen 
garantiert nicht im Bereich 'a' bis 'z'.

Nur, wenn beide Seiten des Vergleichs erst zur Laufzeit feststehen oder 
es nur ein Vergleich wäre (x < 'z'), dann steht es nicht zur Compilezeit 
fest.

: Bearbeitet durch Moderator
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Um das mal an einem Beispiel zu demonstrieren:
1
#ifndef CAST
2
# define CAST(x) (x)
3
#endif
4
5
int
6
myislower(int x)
7
{
8
        return CAST(x) >= 'a' && CAST(x) <= 'z';
9
}

Compiliert mit gcc -Os -S:
1
myislower:
2
.LFB0:
3
        .cfi_startproc
4
        subl    $97, %edi
5
        xorl    %eax, %eax
6
        cmpb    $25, %dil
7
        setbe   %al
8
        ret

Und mit gcc -Os -S -D'CAST(x)=(unsigned char)(x)':
1
myislower:
2
.LFB0:
3
        .cfi_startproc
4
        subl    $97, %edi
5
        xorl    %eax, %eax
6
        cmpl    $25, %edi
7
        setbe   %al
8
        ret

Allerdings: beim AVR-GCC kommt wirklich leicht unterschiedlicher Code 
raus:
1
myislower:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0
6
        mov r19,r25
7
        mov r18,r24
8
        subi r18,97
9
        sbc r19,__zero_reg__
10
        ldi r24,lo8(1)
11
        ldi r25,0
12
        cpi r18,26
13
        cpc r19,__zero_reg__
14
        brlo .L2
15
        ldi r25,0
16
        ldi r24,0
17
.L2:
18
/* epilogue start */
19
        ret

vs.
1
myislower:
2
/* prologue: function */
3
/* frame size = 0 */
4
/* stack size = 0 */
5
.L__stack_usage = 0
6
        subi r24,lo8(-(-97))
7
        ldi r18,lo8(1)
8
        ldi r19,0
9
        cpi r24,lo8(26)
10
        brlo .L2
11
        ldi r19,0
12
        ldi r18,0
13
.L2:
14
        mov r25,r19
15
        mov r24,r18
16
/* epilogue start */
17
        ret

Das ist die Optimierung also nicht ganz so gut.

clang (auf dem Host) generiert auch für beide Fälle identischen Code.

von Philipp Klaus K. (pkk)


Lesenswert?

Jörg W. schrieb:
> Philipp Klaus K. schrieb:
>> Somit kann SDCC diesen cast nicht einfach als Optimierung selbst
>> einfügen.
>
> Doch, kann er: negative Zahlen (bei signed char) kann er von vornherein
> alle als "islower" ausschließen, zumindest bei ASCII, denn sie liegen
> garantiert nicht im Bereich 'a' bis 'z'.
>
> Nur, wenn beide Seiten des Vergleichs erst zur Laufzeit feststehen oder
> es nur ein Vergleich wäre (x < 'z'), dann steht es nicht zur Compilezeit
> fest.

Aber das Argument von islower() ist nun mal ein int, kein signed char. 
Und er kann damit eben nicht den Cast einfügen, denn mit Cast (und 
üblichem Wrappping) ist islower(-153) true, während es ohne Cast false 
ist.

Und SDCC hat selbst kein Wissen über islower(); aus SDCC-Sicht ist das 
einfach eine Funktion.

Ja, man könnte in SDCC Wissen zu islower() einbauen (wie es begrenzt 
z.B. bei printf und memcpy der Fall ist). Aber das wäre dann eine 
islower()-spezifische Optimierung, keine allgemein anwendbare.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Philipp Klaus K. schrieb:
> Und er kann damit eben nicht den Cast einfügen, denn mit Cast (und
> üblichem Wrappping) ist islower(-153) true

Nein, warum sollte es? Die Zahl -153 ist kleiner als 'a', damit ist es 
nicht true.

Ansonsten: sie mein Beispiel, und da ich die Funktion myislower genannt 
habe, greift auch nicht "implizites Wissen".

von Philipp Klaus K. (pkk)


Lesenswert?

Jörg W. schrieb:
> Um das mal an einem Beispiel zu demonstrieren:
>
>
1
> #ifndef CAST
2
> # define CAST(x) (x)
3
> #endif
4
> 
5
> int
6
> myislower(int x)
7
> {
8
>         return CAST(x) >= 'a' && CAST(x) <= 'z';
9
> }
10
>
>
> Compiliert mit gcc -Os -S:
>
>
1
> myislower:
2
> .LFB0:
3
>         .cfi_startproc
4
>         subl    $97, %edi
5
>         xorl    %eax, %eax
6
>         cmpb    $25, %dil
7
>         setbe   %al
8
>         ret
9
>
>
> Und mit gcc -Os -S -D'CAST(x)=(unsigned char)(x)':
>
>
1
> myislower:
2
> .LFB0:
3
>         .cfi_startproc
4
>         subl    $97, %edi
5
>         xorl    %eax, %eax
6
>         cmpl    $25, %edi
7
>         setbe   %al
8
>         ret
9
>
> […]
>
> clang (auf dem Host) generiert auch für beide Fälle identischen Code.

Das ist kein identischer Code: einmal cmpl, einmal cmpb. Hier noch ein 
einfaches Programm für den Host:
1
#include <stdio.h>
2
3
#define CAST1(x) (x)
4
5
int
6
myislower1(int x)
7
{
8
        return CAST1(x) >= 'a' && CAST1(x) <= 'z';
9
}
10
11
#define CAST2(x) (unsigned char)(x)
12
13
int
14
myislower2(int x)
15
{
16
        return CAST2(x) >= 'a' && CAST2(x) <= 'z';
17
}
18
19
int main(void)
20
{
21
  printf("%d %d\n", myislower1(-158), myislower2(-158));
22
}

ergibt, wie zu erwarten (sowohl mit GCC als auch LLVM hier):
1
philipp@notebook6:/tmp$ ./a.out 
2
0 1

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Philipp Klaus K. schrieb:
> Das ist kein identischer Code: einmal cmpl, einmal cmpb

Stimmt. :/

Aber: damit ergibt natürlich der Typecast auf unsigned char ein falsches 
Ergebnis, denn -158 ist natürlich kein ASCII-Kleinbuchstabe.

von Philipp Klaus K. (pkk)


Lesenswert?

Jörg W. schrieb:
> Philipp Klaus K. schrieb:
>> Das ist kein identischer Code: einmal cmpl, einmal cmpb
>
> Stimmt. :/
>
> Aber: damit ergibt natürlich der Typecast auf unsigned char ein falsches
> Ergebnis, denn -158 ist natürlich kein ASCII-Kleinbuchstabe.

Kein falsches Ergebnis, da der C-Standard explizit sagt, dass das 
Verhalten undefiniert ist, wenn ein Wert übergeben wird, der weder EOF 
ist, noch in einen unsigned char passt.

Philipp

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Philipp Klaus K. schrieb:
> Kein falsches Ergebnis, da der C-Standard explizit sagt, dass das
> Verhalten undefiniert ist, wenn ein Wert übergeben wird, der weder EOF
> ist, noch in einen unsigned char passt.

Na gut. :)

von mh (Gast)


Lesenswert?

Philipp Klaus K. schrieb:
> Kein falsches Ergebnis, da der C-Standard explizit sagt, dass das
> Verhalten undefiniert ist, wenn ein Wert übergeben wird, der weder EOF
> ist, noch in einen unsigned char passt.

Bleibt noch die Frage nach dem Wert von EOF ;-)

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.