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
bytefeld[200];
2
3
voidsetup()
4
{
5
Serial.begin(9600);
6
7
8
// alle Zellen vorbesetzen
9
for(byte&data:feld)data=0xFF;
10
11
12
intresult=0;
13
for(bytedata:feld)result+=data;
14
Serial.println(result);
15
16
// Serial.println(4711); // Kontrollausgabe
17
}
18
19
voidloop(){}
> 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);
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
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
bytex[200];
2
intresult;
3
4
voidsetup()
5
{
6
Serial.begin(115200);
7
Serial.println("\nStart");
8
9
// alle Zellen vorbesetzen
10
for(byte&data:x)data=0xFF;
11
12
for(bytei=127;i<133;i++)
13
{
14
summieren(i);
15
}
16
17
}
18
19
voidloop()
20
{
21
}
22
23
voidsummieren(constbytecount)
24
{
25
// summe über alle Zellen
26
result=0;
27
for(bytei=0;i<count;i++)
28
{
29
result=result+x[i];
30
//Serial.println(result); // ohne diese Zeile falscher Überlauf
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...
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...
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.
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.
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.
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...
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.
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)
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.
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.
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_tPrint::print(longn,intbase)
2
{
3
if(base==0){
4
returnwrite(n);
5
}elseif(base==10){
6
if(n<0){
7
intt=print('-');
8
n=-n;
9
returnprintNumber(n,10)+t;
10
}
11
returnprintNumber(n,10);
12
}else{
13
returnprintNumber(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.
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:
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.
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.
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
unberücksichtigt blieben. So einfach kann man das nicht abtun.
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.
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.
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.
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.
Ü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.
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.
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.
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. ;-)
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.
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...
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);
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.
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
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.
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.
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...
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.
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.
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...
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.
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.
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.
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.
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.
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.
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!
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.
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.
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.
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.
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.
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!
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.
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!
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.
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.
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...
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
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.
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.
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.
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?
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 ;-)
-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.
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?
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
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.
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
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.
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.
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:
..\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
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.
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.
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
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".
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
Stefan ⛄ F. schrieb:> Ich ich Software mit offensichtlichen Fehlfunktionen abliefere
Sollte heißen: Wenn ich Software mit offensichtlichen Fehlfunktionen
abliefere
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.
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.
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.
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.
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
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_tPrint::print(intn,intbase)
2
{
3
returnprint((long)n,base);
4
}
das eines für "long" aufruft
1
size_tPrint::print(longn,intbase)
2
{
3
if(base==0){
4
returnwrite(n);
5
}elseif(base==10){
6
if(n<0){
7
intt=print('-');
8
n=-n;
9
returnprintNumber(n,10)+t;
10
}
11
returnprintNumber(n,10);
12
}else{
13
returnprintNumber(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_tprintNumber(unsignedlong,uint8_t);
Hier sieht man den Teil des im Code-Listing integrierten Quellcodes von
1
size_tprint(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.
Hallo,
ohne Irrtümer gebe es keine Weiterentwicklung. Entschuldigung für meine
Dummheit. Ich danke dir dennoch das du mir meinen Denkfehler aufgezeigt
hast.
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 :)
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
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ß,
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.
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.
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.
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.
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.
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.
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
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...
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)
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.
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...
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.
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.
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.
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.
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
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.)
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?
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."
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.
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.
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.
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.
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.
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.
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
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.
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.)
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.
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:
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((unsignedchar)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:
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.
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.
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
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.
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
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.
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.
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.
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.
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
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 ’¢¢â€šÂ¬¢â€žÂ¢’Æ‬™’¢¢â€šÂ¬‚ ’Æâ€â€šÃ‚¢’¢¢â‚¬Å¡‚¬’¢¢â‚¬Å¾‚¢’Æ‬™’†¢â‚¬â„¢’Æâ€Â¢Ã¢â€šÂ¬Ã…¡’â₂¢’Æ‬™’â₂¢’Æâ€â€šÃ‚¢’¢¢â‚¬Å¡‚¬’…‚¡’Æâ€Â¢Ã¢â€šÂ¬Ã…¡’â₂¬’Æ‬™’¢¢â€šÂ¬…¡’Æâ€Â¢Ã¢â€šÂ¬Ã…¡’â₂ ’Æ‬™’†¢â‚¬â„¢’Æâ€Â¢Ã¢â€šÂ¬Ã‚ ’¢¢â€šÂÂ�
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;
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.
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...
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.
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.
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
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.
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:
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.
> 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.
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.
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.
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.
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.
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".
Jörg W. schrieb:> Um das mal an einem Beispiel zu demonstrieren:>>
1
>#ifndefCAST
2
>#defineCAST(x)(x)
3
>#endif
4
>
5
>int
6
>myislower(intx)
7
>{
8
>returnCAST(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:
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.
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
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. :)
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 ;-)