Hallo zusammen,
ich habe gerade einen Quelltext, der Teil eines größeren Projekts ist.
Der Schnipsel, dessen Ergebnis mich erstaunt, ist:
1
boolr=This->Main.softLimitsRequest;
2
bools=This->Ctrl.softLimitsEnabled;
3
4
int8_ta=((r!=0)<<2)|((s!=0)<<1)|1;
5
printf("\n0x%x",a);
Die Konsolenausgabe lautet für den ARM-GCC erwartungsgemäß, je nach
Füllung der beiden Variaben r und s 0x1, 0x3, 0x5 usw. Beim MinGW lautet
die Konsolenausgabe 0x9, 0xd, 0xf. Ich versuche gerade, die Logik
dahinter zu verstehen. Welche Regel erlaubt dem Compiler, das
unerwartete Ergebnis zu erzeugen?
P.S.: Ich kann der Ergebnis nur mit einer Zuweisung von r und s aus dem
struct reproduzieren. Mit einem Literal kommt immer das Erwartete
heraus.
P.P.S.: Mit dem "klassischen" Idiom
1
int8_ta=((!!r)<<2)|((!!s)<<1)|1;
kommt das gleiche unerwartete Ergebnis reproduzierbar heraus.
Abgesehen davon, daß %x einen unsigned-Wert erwartet, und der sicherlich
akademisch interessanten Frage, warum der Effekt auftritt, bleibt vor
allem die entscheidende Frage: Warum in aller Welt schreibt man solchen
Code? Bist du im Nationalteam zur obfuscated-C-Olympiade?
Oliver
Frank K. schrieb:> Das Problem ist, dass Du Dich daraf verlässt, dass true nach 1> evaluiert.
Wenn es mit einem standardkonformen C99 Compiler kompiliert wird, dann
darf er das auch.
mh schrieb:> Wie wäre es mit einem minimalen, compilierbaren Beispiel, das den Fehler> enthält?
Ich denke, es wäre sehr schön. Leider gelingt es mir nicht, das
Verhalten vom konkreten Projekt zu lösen. Ich kann es in eine isolierte
Funktion bringen, aber sobald diese in einer anderen Quelltext-Datei
steht, ist das Verhalten nicht mehr das oben gezeigte.
Walter T. schrieb:> mh schrieb:>> Wie wäre es mit einem minimalen, compilierbaren Beispiel, das den Fehler>> enthält?>> Ich denke, es wäre sehr schön. Leider gelingt es mir nicht, das> Verhalten vom konkreten Projekt zu lösen. Ich kann es in eine isolierte> Funktion bringen, aber sobald diese in einer anderen Quelltext-Datei> steht, ist das Verhalten nicht mehr das oben gezeigte.
Wie sollen wir dir dann helfen? Aber dann ist das Problem wohl das
restliche Projekt?!?
mh schrieb:> Wie sollen wir dir dann helfen?
Schritt 1: Ist das beobachtete Verhalten wirklich ein Fehler, oder nur
eine sehr spitzfindige Auslegung des C-Standards? Um das festzustellen,
sollte der sichtbare Schnipsel ausreichen.
Ist es ein Fehler, werde ich wohl mehr Mühe darauf verwenden müssen,
entweder den Fehler im Bugtracker zu finden, und/oder isolierbares
Beispiel zu bauen. Der Workaround für meinen Zweck ist schon fertig.
Ist es "nur" eine unerwartete Interpretation, besteht der nächste
Schritt wohl in der Entwicklung einer sauberen, noch kugelsicherren
Variante.
Walter T. schrieb:> Ist das beobachtete Verhalten wirklich ein Fehler,
Wenn die Werte/Typen in Ordnung sind, mit denen r und s initialisiert
werden, ist der Schnipsel so in Ordnung.
Walter T. schrieb:> oder nur eine sehr spitzfindige Auslegung des C-Standards?
Hast du selbst mal in den Standard geschaut? Die Teile, die hier
relevant sind, beziehen sich fast nur auf "Conversions" und
"Expressions" und sind relativ schnell überfliegbar.
Walter T. schrieb:> Um das festzustellen, sollte der sichtbare Schnipsel ausreichen.
Die Antwort ist dann aber auch nur für schnipselspezifische Bedingungen
gültig.
Nachtrag2: Der Schnipsel läßt behält auch in anderen Quelltext-Dateien
sein beobachtetes Verhalten. Den Wechsel zum erwarteten Verhalten zeigt
er erst dann, wenn stdbool.h nicht mehr im include tree ist.
Walter T. schrieb:> Nachtrag2: Der Schnipsel läßt behält auch in anderen Quelltext-Dateien> sein beobachtetes Verhalten. Den Wechsel zum erwarteten Verhalten zeigt> er erst dann, wenn stdbool.h nicht mehr im include tree ist.
Und wie soll Quelltext, der bool enthält, compilieren, wenn stdbool.h
nicht includiert wird?
mh schrieb:> Und wie soll Quelltext, der bool enthält, compilieren, wenn stdbool.h> nicht includiert wird?
Da hätten wir das Problem wohl gefunden. Vielleicht hilft es auch, wenn
der Compiler Aufruf gezeigt wird inklusive Version des Compilers.
Ich kann dein Ergebnis mit dem GCC ("gcc (Debian 8.3.0-6) 8.3.0")
nicht nachvollziehen (Minimalbeispiel):
1
#include <stdint.h>
2
#include <stdio.h>
3
#include <stdbool.h>
4
5
6
volatile bool r;
7
volatile bool s;
8
9
int main()
10
{
11
int x;
12
13
scanf("%d", &x);
14
r = (x != 0) ? true : false;
15
scanf("%d", &x);
16
s = (x != 0) ? true : false;
17
18
int8_t a = ( (r != 0)<<2 ) | ( (s != 0)<<1 ) | 1;
19
printf("0x%x\n", a);
20
21
return 0;
22
}
Wenn in der restlichen Software mit ähnlichen "Tricks" gesegnet ist,
darf ich mal vermutend unterstellen, dass s und r nicht korrekte
boolsche Werte (1,0) enthalten.
Hatte ich auch schon mal mit C++ (PC, MS Visual Studio):
Bool-Werte von binärem Stream gelesen, in der Speicherzelle stand ein
Wert ungleich 0/1 und der doofe Debugger hat alles ungleich 0 als true
angezeigt.
If/Else ging, aber Verknüpfungen wie &&, || lieferten Unsinn.
Hat ne Weile gedauert bis ich es gefunden habe, natürlich zuerst an
einen Compilerbug geglaubt ;-)
Frank K. schrieb:> Das Problem ist, dass Du Dich daraf verlässt, dass true nach 1> evaluiert.
Der Vollständigkeit halber: In C++ ist das so. Wenn ein bool nach int
konvertiert wird - was durch den Vergleich mit 0 forciert wird - kommt
immer 1 oder 0 raus. Das (r != 0) ist in C++ sinnlos redundant und das
gleiche wie "r".
Es klingt jedenfalls stark danach als würde im struct etwas verkehrtes
stehen und die Annahmen des Compilers, dass da nur 1/0 stehen kann,
verletzt werden.
Dr. Sommer schrieb:> Frank K. schrieb:>> Das Problem ist, dass Du Dich daraf verlässt, dass true nach 1>> evaluiert.>> Der Vollständigkeit halber: In C++ ist das so. Wenn ein bool nach int> konvertiert wird - was durch den Vergleich mit 0 forciert wird - kommt> immer 1 oder 0 raus. Das (r != 0) ist in C++ sinnlos redundant und das> gleiche wie "r".>> Es klingt jedenfalls stark danach als würde im struct etwas verkehrtes> stehen und die Annahmen des Compilers, dass da nur 1/0 stehen kann,> verletzt werden.
Nur darf der Compiler das nicht annehmen, sondern sollte sich an den
Standard halten...
C99 6.5.9 3 "The == (equal to) and != (not equal to) operators are
analogous to the relational operators except for their lower
precedence.93) Each of the operators yields 1 if the specified relation
is true and 0 if it is false. The result has type int."
C++2017 8.5.9 6 "... each of the operators shall yield true if the
specified relationship is true and false if it is false". Hinzukommen
dann die "Shift Operators" 8.5.7, die die "Integral Promotions"
vorschreiben und in 7.6 definiert sind. 7.6 6 "A prvalue of type bool
can be converted to a prvalue of type int, with false becoming zero and
true becoming one".
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4659.pdf
@ Walter
Welches sind denn nun die Versionen des verwendeten AVR-GCC und des
MinGW? Ist ha schon danach gefragt worden, aber bisher keine Antwort.
Und gibt es denn keinerlei Warnungen beim Complieren? Sicherheitshalber
mal gucken, ob alle Warnungen eingeschaltet sind. Beim GCC sollte das
-wall sein. Beim MinGW weiß ich es nicht.
Hast Du gemeint, dass im Quellcode nicht mehr "#include <stdbool.h>
steht, als Du sagetest, es sei nicht mehr "im include tree ist"? Wie
genau hast Du stdbool.h entfernt?
Das der Code funktioniert, falls stdbool nicht mehr includes wird (falls
das so zu verstehen ist) ist ein sehr merkwürdiges Phänomen, meine ich.
Naja. Was mir so alles merkwürdig vorkommt... :-)
Erst mal gucken, was die Versionen sind und ob es Warnungen gibt und wie
das mit der H-Datei nun genau lief.
Walter T. schrieb:> Schritt 1: Ist das beobachtete Verhalten wirklich ein Fehler
Ich würde es erstmal für einen solchen halten.
Meiner Meinung nach ist sogar bereits der Test der bools auf != 0
unnötig, denn für den Typ bool ist garantiert, dass er nur die Werte 0
und 1 haben kann. Das müsste ich allerdings bei Interesse selbst nochmal
gegenlesen.
mh schrieb:> Und wie soll Quelltext, der bool enthält, compilieren, wenn stdbool.h> nicht includiert wird?
Das frage ich mich allerdings auch … man könnte natürlich noch _Bool
statt bool benutzen, dann braucht man den Header nicht. Da _Bool im
implementation namespace liegt, ließ sich dieser Typ in C99 konfliktfrei
allgemein hinzufügen, während bool eigentlich im application namespace
ist, und daher nur offenbart wird, wenn man <stdbool.h> inkludiert.
Hm. Dreht es sich nicht vielmeher um die Frage, ob das Ergebnis eines
Vergleichs "!=", "==" etc, grundsätzlich entweder 0 oder nicht 0 ist?
Falls ja, wäre es doch egal, ob bool nun 0 und 1 oder 0 und >0 ist oder
nicht.
Das Probelm ist ja anscheinend dass MinGW bei "!=", "==" usw. für true
eben auch Werte über 1 als Ergebnis verwendet.
Oder verstehe ich das Problem gerade nicht?
Ooops. Zu wenig Kaffe. :-)
Es sollte heissen:
Dreht es sich nicht vielmeher um die Frage, ob das Ergebnis eines
Vergleichs "!=", "==" etc, grundsätzlich entweder 0 oder 1 oder 0 oder
nicht 0 ist?
Es ist doch egal, ob bool nun 0 und 1 oder 0 und >0 ist oder
nicht. Hauptsache es lässt sich in integer umwandeln.
Das Problem ist ja anscheinend dass MinGW bei "!=", "==" usw. für true
eben auch Werte über 1 als Ergebnis verwendet.
Oder verstehe ich das Problem gerade nicht?
Au weia. Doch schon sehr spät.
Ich hoffe man kann ahnen, was ich meine, aber ich will Euch nicht mit
noch mehr Korrekturen auf die Nerven gehen und lese heute nur noch mit.
Sorry.
Walter T. schrieb:> Ist es ein Fehler
Glaube ich eher nicht, s.o.
Die Zahlen dürfen oder sollen nicht negativ werden, diesen Anspruch muss
man irgendwo im Code wiederfinden.
Welche Genauigkeit tatsächlich benötigt wird, kann man nur im
Gesamtzusammenhang/Lebensraum erkennen.
Walter T. schrieb:> bool r = This->Main.softLimitsRequest;> bool s = This->Ctrl.softLimitsEnabled;>> int8_t a = ( (r != 0)<<2 ) | ( (s != 0)<<1 ) | 1;> printf("\n0x%x", a);
Stehen diese Zeilen wirklich direkt hintereinander?
Oder stehen die ersten beiden und die letzten beiden jeweils in
verschiedenen Übersetzungseinheiten?
Walter T. schrieb:> Den Wechsel zum erwarteten Verhalten zeigt> er erst dann, wenn stdbool.h nicht mehr im include tree ist.
Evtl. gibt es in deiner Software zwei verschiedene bool-Typen: Den aus
stdbool.h und zusätzlich einen, der mit
1
#define bool char
o.ä. definiert wurde.
Sind die Variablen r und s bei ihrer Initialisierung oder Zuweisung vom
Typ char, wird der zugewiesene Wert nicht auf 0/1 reduziert.
Sind sie hingegen bei der Abfrage r!=0 und s!=0 (in einer anderen
Übersetzungseinheit) vom Typ bool aus stdbool.h, könnte der Compiler den
Vergleich mit 0 wegoptimieren, da dieser bei ordnungsgemäßer Verwendung
des bool-Typs ohne Wirkung ist. Gleiches gilt für die Verwendung von !!.
Beides zusammen führt dazu, dass im Ausdruck ((r!=0)<<2)|((s!=0)<<1)|1
die Teilausdrücke r!=0 und s!= von 0 und 1 verschiedene Werte haben
können, was das von dir beobachtete unerwartete Ergebnis liefert.
Was wird denn mit
1
printf("r=%d s=%d\n",r,s);
ausgegeben?
PS: Vielleicht hast du in deinem Programm auch ganz einfach irgendwo
einen Array-Überlauf o.ä., der r und s mit nicht bool-konformen Werten
überschreibt.
Guten Morgen,
ich habe den Fehler gefunden. Es war ein grober Fehler meinerseits. An
irgendeiner, völlig anderen Stelle, wird eine boolesche Variable (nennen
wir sie b0) innerhalb eines Structs nicht initialisiert. Warum das keine
Warnung gegeben hat, muß ich noch suchen. Diese Variable hat beim Start
den Wert 2. Im Debugger leider nicht zu sehen, aber ein printf() gibt
bei b0 0x2 und bei !b0 0x3 aus.
Die Variable This->Main.softLimitsRequest aus dem obigen Schnipsel
entsteht aus logischen Verknüpfungen, in denen eine Quelle b0 ist. Alle
Zuweisungen in andere boolesche Variablen, logische Verknüpfungen und
das !!x-Idiom hindern nicht die Fortpflanzung des unerlaubten Werts.
Für mich bedeutet das:
1. Nein, es ist kein Kompilerfehler (deswegen ist die Frage nach der
Version wohl hinfällig). Nur eine fehlende Warnung, trotz -Wall,
-Wpedantic. Bei einem unitialisierten Wert hat der Compiler leider das
absolute Recht, jeden Blödsinn zu machen.
2. Die Erkenntnis, daß die Guards der Form !!x und x!=0 bei booleschen
Variablen hinfällig sind. Sie werden ohnehin wegoptimiert
(Optimierungsstufe ist -O1).
3. Ich brauche einen neuen Weg, die derart weitschweifige Fortpflanzung
eines solchen Fehlers künftig zu verhindern. Static assertions werden
vermutlich genauso wirkungslos sein. Ob normale Assertions nützen, oder
einfach wegoptimiert werden, muß ich im Laufe des Tages mal
ausprobieren.
Danke für die Diskussion!
P.S.:
Yalu X. schrieb:> Stehen diese Zeilen wirklich direkt hintereinander?
Ja, der Schnipsel ist unmittelbar über die Zwischenablage kopiert.
Walter T. schrieb:> An irgendeiner, völlig anderen Stelle, wird eine boolesche Variable> (nennen wir sie b0) innerhalb eines Structs nicht initialisiert.
Puh, dass das so eine böse Auswirkung hat, ist natürlich interessant.
Andererseits, gut zu wissen, dass es eben kein Compilerfehler ist – und
eigentlich auch gut zu wissen, dass der Compiler selbst bei solchen
„Angst-Pessimierungen“ wie “<bool> != 0 ” oder “!!<bool>” keinen
pessimierten Code generiert.
> Warum> das keine Warnung gegeben hat, muß ich noch suchen.
Das wäre allerdings wirklich interessant.
Jörg W. schrieb:> auch gut zu wissen, dass der Compiler selbst bei solchen> „Angst-Pessimierungen“ wie “<bool> != 0 ” oder “!!<bool>” keinen> pessimierten Code generiert.
Was ist dann aber wenn jemand folgenden Ausdruck schreibt:
kaputte_boolvariable ? 1 : 0
Wird er das auch optimistisch wegoptimieren?
Jörg W. schrieb:> Bernd K. schrieb:>> Wird er das auch optimistisch wegoptimieren?>> Warum denn nicht? Ist doch letztlich nichts anderes als die anderen> Ausdrücke.
Na dann sieht man aber alt aus wenn man wirklich mal solche Daten in
einer bool-Variablen bekommt. Egal was ich mache, der Compiler könnte
sich auf jederzeit den Standpunkt stellen daß das was da als bool
hereinkam eh nur 1 oder 0 sein kann und optimiert alles weg was das
hätte sicherstellen können.
Bernd K. schrieb:> Na dann sieht man aber alt aus wenn man wirklich mal solche Daten in> einer bool-Variablen bekommt.
Ja, natürlich.
Man kann das aber nicht ohne weiteres „bekommen“. Neben dem hier
gefundenen Fall einer nicht initialisierten Variablen könnte es maximal
über einen Typecast da hin gekommen sein. Typecasts sollte man natürlich
immer nur mit der notwendigen Vorsicht und möglichst selten benutzen.
Ansonsten ist es ja gerade der Sinn dieses Datentyps, dass er nur die
Werte 0 und 1 annehmen kann.
Walter T. schrieb:> 2. Die Erkenntnis, daß die Guards der Form !!x und x!=0 bei booleschen> Variablen hinfällig sind. Sie werden ohnehin wegoptimiert
Ist das so? Ist !!a nicht immer 0 oder 1? Wann genau?
In C oder C++?
Wenn a bool ist, oder auch int?
A. S. schrieb:> Ist das so?
Ja, aber du hast es erstens völlig aus dem Zusammenhang gerissen und
zweitens nicht richtig gelesen. Gehe also einfach nochmal zurück auf
"Los!", und starte von vorn mit dem Thread.
Bernd K. schrieb:> Na dann sieht man aber alt aus wenn man wirklich mal solche Daten in> einer bool-Variablen bekommt.
Vielleicht ist jeder dieser Wege undefined behaviour?
A. S. schrieb:> Ist das so? Ist !!a nicht immer 0 oder 1?
In einem korrekten Programm schon. Aber bei undefined behavior kann eben
alles passieren. Das Lesen einer nicht initialisierten Variablen ist
undefined behavior.
Jörg W. schrieb:> Bernd K. schrieb:>> Na dann sieht man aber alt aus wenn man wirklich mal solche Daten in>> einer bool-Variablen bekommt.>> Ja, natürlich.>> Man kann das aber nicht ohne weiteres „bekommen“.
Naja, ich denke so solche Fälle wie ich schreibe Code der gegen anderen
Code gelinkt wird und irgendwo bekomme ich von dort ein verschmutztes
bool übergeben, als bool deklariert. Dann liegt es völlig außerhalb
meiner Macht aber es wird so aussehen als ob mein eigener Code irgendwo
tief im innern irgendwo Mist baut, das ist das Meterial aus dem
Debugging-Albträume gemacht sind.
Jörg W. schrieb:> Neben dem hier> gefundenen Fall einer nicht initialisierten Variablen könnte es maximal> über einen Typecast da hin gekommen sei
Kennst Du eine Variante, wie man das provozieren kann, ohne daß der
Compiler das mitbekommt? Könnte man den Fehler selbst triggern, wäre es
auch kein Problem, Gegenmaßnahmen zu testen.
(Ich habe es noch nicht probiert, da gerade am falschen Rechner -
vielleicht geht der naheliegende Weg mit Zeigern oder unions.)
Bernd K. schrieb:> Naja, ich denke so solche Fälle wie ich schreibe Code der gegen anderen> Code gelinkt wird und irgendwo bekomme ich von dort ein verschmutztes> bool übergeben, als bool deklariert.
Wenn der andere Code so schlampig ist, dann kannst du doch auch
sämtliche anderen Fälle von undefined behaviour nicht ausschließen.
Wenn du das wirklich fürchten musst, dann sollte es eben zu dem
Fremdcode gar kein "bool" als Übergabewert geben. Ich würden in solchen
Fällen allerdings den Fremdcode komplett in Frage stellen.
Walter T. schrieb:> Kennst Du eine Variante, wie man das provozieren kann
Nö.
Walter T. schrieb:> vielleicht geht der naheliegende Weg mit Zeigern oder unions
Zeiger würden ja in die Typecast-Geschichte reinlaufen. Ja, damit kann
man sicher x-beliebige Varianten von undefined behaviour provozieren.
Walter T. schrieb:> Kennst Du eine Variante, wie man das provozieren kann, ohne daß der> Compiler das mitbekommt? Könnte man den Fehler selbst triggern, wäre es> auch kein Problem, Gegenmaßnahmen zu testen.
nicht ausprobiert, könnte mir aber std::memcpy bzw. memcpy mit int->bool
vorstellen.
Im Zweifel hat der Compiler immer recht.
Der Compiler ist ja nicht blöd. Jede Zuweisung zu bool wandelt er in 0
oder 1 um. Daher darf er bei der Auswertung davon ausgehen, daß diese
immer nur 0 oder 1 sind. Aus seiner Sicht überflüssige Konvertierungen
schmeißt er alle weg.
Wenn Du ihm von hinten durch die Brust ins Auge abweichende Werte
reinschreibst, kann der Compiler doch nichts dafür.
Der Compiler ist sogar recht intelligent. In der Regel werden Variablen
öfter gelesen, als geschrieben. Daher ist die Konvertierung bei jedem
Schreibzugriff effizienter, als bei jedem Lesezugriff. Und bei jedem
Schreiben zur Compilezeit sowieso.
Hier mal mit dem AVR-GCC:
Bemerkenswert! (Ich frage mich, warum mir das noch nicht passiert ist).
Vielleicht sollten wir doch mal versuchen einen Testcode hinzuschreiben.
Jedenfalls:
Widerspricht das aber nicht doch dem Standard?
Falls der Compiler, - soweit ja zurecht -, annimmt, dass bool immer zu 0
oder 1 evaluiert und also Vergleiche mti 0 und nicht 0 wegoptimiert weil
sie redundant sind, dann bleibt doch aber noch die Regel, dass
Vergleiche immer zu 0 oder 1 evaluieren - müssen.
Und diese Regel würde der Compiler doch verletzen, wenn er zwar den
Vergleich wegoptimiert aber nicht dafür sorgt, dass der resultierende
Wert , genau 0 oder 1 ist. Er optimiert ja nicht nur den Vergleich weg
sondern gleichzeitig die Umwandlung des Vergleichsresultats in integer.
Seht Ihr das anders?
P.S. Muss mal in den Standards nachsehen. Deswegen wäre die
Compilerversion doch interessant, weil sich daraus auch ergibt, welcher
Standard anzuwenden ist.
Theor schrieb:> Seht Ihr das anders?
Ja.
Der Compiler darf mit Fug und Recht davon ausgehen, dass die Variable
bereits entweder den Wert 0 oder 1 hat, daher kann er den ganze anderen
Kokolorus getrost wegwerfen.
Dass die Variante im vorliegenden Fall eben nicht 0 oder 1 war, ergab
sich ja lediglich als Folge einer Aktion, die explizit als undefined
behaviour markiert ist. Wenn wir Compiler dazu bringen wöllten, alle
diese Fälle nun auch noch jedesmal auf Plausibilität zu testen, dann
würde sich die Fraktion der Assemblerprogrammierer völlig zu Recht
wieder drüber beklagen, wie lahmarschig der vom Compiler generierte Code
doch sei.
Theor schrieb:> Seht Ihr das anders?
Ja!
Theor schrieb:> P.S. Muss mal in den Standards nachsehen. Deswegen wäre die> Compilerversion doch interessant, weil sich daraus auch ergibt, welcher> Standard anzuwenden ist.
Ich weiß nicht wie es bei K&R aussieht, aber ab C89 sollte sich da
nichts mehr geändert haben.
Jörg W. schrieb:> [,,,]> Dass die Variante im vorliegenden Fall eben nicht 0 oder 1 war, ergab> sich ja lediglich als Folge einer Aktion, die explizit als undefined> behaviour markiert ist.> ]...]
Falls Du damit sagen, willst, dass aus undefined behavior eben undefined
behavior folgt, überzeugt mich das. OK.
Naja. Bisher habe ich wohl entweder mehr Glück als Verstand gehabt oder
umgekehrt. :-)
Theor schrieb:> Naja. Bisher habe ich wohl entweder mehr Glück als Verstand gehabt
Du wirst wohl keine Schweinereien à la (void*) in Deinem Code haben und
alle Warnungen à la:
"x.c:35: warning: 'x' is used uninitialized in this function"
beseitigt haben.
Bei sauberem Code muß man keine Angst vor solchen Fehlern haben.
@ Jörg
Hach. Ich weiß nicht. Daran gefällt mir doch was nicht.
Ich will Dir keinesfalls auf die Nerven gehen oder rummeckern. Und ich
wei0 (oder glaube mich zu erinnern), dass Du am GCC mitarbeitetst und
daher vermutlich mehr in der Materie steckst, als ich. Aber vielleicht
hast Du ja etwas Gedudld und magst nochmal antworten.
Das mit dem undefined behavior ist an sich unstrittig.
Aber was ist mit der Regel das Vergleiche immer entweder zu 0 oder zu 1
evaluieren. (Siehe das Zitat aus C99 hier:
Beitrag "Re: C (MinGW): Unerwartete Bool-Auswertung")
Diese Regel dürfte doch eigentlich durch die Optimierung nicht verletzt
werden. Oder doch? Ergibt sich das aus dem Standard?
Peter D. schrieb:> Schweinereien à la (void*)
Was ist an (void *) eine Schweinerei? Gibt es eine bessere Möglichkeit,
ungenutzte Variablen in einer Funktion zu behandeln?
Peter D. schrieb:> Theor schrieb:>> Naja. Bisher habe ich wohl entweder mehr Glück als Verstand gehabt>> Du wirst wohl keine Schweinereien à la (void*) in Deinem Code haben
Schon, aber extrem selten. Ich caste auch Buffer-Zeiger in
Strukturzeiger und umgekehrt. Aber gaaaaanz vorsichtig. :-)
> und> alle Warnungen à la:> "x.c:35: warning: 'x' is used uninitialized in this function"> beseitigt haben.
Ja- Das auch.
> [,,,]
Hmm.
Dennoch sticht der Einwand von Jörg, dass man, wollte man auch im
optimierten Fall, das Ergebnis von entweder 0 oder 1 erzwingen, gerade
diese Optmierungen dann effektiv nie machen dürfte.
Unglücklich, aber folgerichtig.
Hat sich erledigt, Jörg. Danke.
Theor schrieb:> Ich caste auch Buffer-Zeiger in> Strukturzeiger und umgekehrt.
Das ist in Ordnung solange du nicht den Strukturzeiger dereferenzierst,
sonst UB.
Ich nähere mich gerade dem Minimalbeispiel. Die Datei im Anhang wird
ohne Warnung kompiliert, nutzt aber die Variable b3 vor der
Initialisierung.
Auf Anhieb kann ich keine weitere Zeile löschen, ohne daß der Compiler
merkt, dass State.b3 uninitialisiert bleibt.
Jetzt habe ich erst einmal ein Mittagessen verdient, dann schaue ich
weiter.
Damit compiliert es unabhängig von deiner Umgebung.
clang schafft es es bis Version 8 nicht, eine Warnung zu generieren.
GCC 5 ebenfalls nicht, GCC 8 wirft eine Warnung:
1
foo.c: In function 'foo':
2
foo.c:77:36: warning: 'State.b3' is used uninitialized in this function [-Wuninitialized]
Theor schrieb:> Aber was ist mit der Regel das Vergleiche immer entweder zu 0 oder zu 1> evaluieren. (Siehe das Zitat aus C99 hier:> Beitrag "Re: C (MinGW): Unerwartete Bool-Auswertung")Arc N. schrieb:> Nur darf der Compiler das nicht annehmen, sondern sollte sich an den> Standard halten...
Der Compiler trifft keine Annahmen, denn er ist kein Mensch. Er geht
schlichtweg davon aus, daß das, was er einmal reingeschrieben hat, auch
genauso zurück gelesen werden kann. Für die korrekte Funktion ist es
unerheblich, ob er beim Schreiben nach bool konvertiert oder erst beim
Lesen der bool-Variablen.
Wenn einer am Compiler vorbei einen Bool falsch setzt, dann ist er auch
ganz allein dafür verantwortlich. Ursächlich für das Fehlverhalten ist
also nicht der Compiler, sondern der Programmierer mit seiner Annahme,
der Compiler würde überflüssige Konvertierungen einbauen.
Jörg W. schrieb:> Ich habe mal dein "intmath.h" ersetzt
Danke! Das hatte ich wohl übersehen.
Peter D. schrieb:> Sowas schreibt man besser:
Erst einmal: Ja. Du hast Recht. Dieses sinnlose Beispiel sieht so aus,
wie es aussieht, weil ich ein längeres Beispiel so lange zusammengekürzt
habe, daß das Fehlen der Warnung erhalten blieb. "In der Realität"
werden fast alle Felder des structs mit Variablen, nicht mit Konstanten
initialisiert.
Jörg W. schrieb:> clang schafft es es bis Version 8 nicht, eine Warnung zu generieren.> GCC 5 ebenfalls nicht, GCC 8 wirft eine Warnung
Danke fürs Gegentesten. Dann kann ich das Problem wohl auf meinen GCC
5.1.0 zurückführen. Es wird wohl mal Zeit für ein Update.
Ich werde mich künftig an den Tipp von PeDa halten, des Struct aus
Prinzip erst einmal leer zu initialisieren - dann sollte diese Falle
auch bei alten Compilerversionen nicht mehr auftauchen.
Jörg W. schrieb:> A. S. schrieb:>> Ist das so?>> Ja, aber du hast es erstens völlig aus dem Zusammenhang gerissen und> zweitens nicht richtig gelesen. Gehe also einfach nochmal zurück auf> "Los!", und starte von vorn mit dem Thread.
Jörg, ich habe den Thread aufmerksam verfolgt. Kenne aber die
Versionshistorie der Compiler nicht. Daher auf eine Frage
zusammengefasst (für int sind sie dann eh hinfällig):
1) Darf jeder C/C++-Kompiler bei Variablen vom Typ bool (seit es das
jeweils gibt) annehmen, dass diese immer true oder false enthält und
keine abweichende Bitkombination?
Entschuldigung, dass ich hier mal die Absicht der Moderatoren
durchkreuze, den Thread
Beitrag "C (MinGW): Unerwartete Bool-Auswertung"
nur noch für angemeldete Benutzer offen zu lassen. (Da gabs wohl ein
paar Provokateure).
Ich bin nur sehr an Peters Stellungnahme interessiert.
(Falls gewollt mag dieser Beitrag auch in den Thread verschoben werden).
Peter D. schrieb:> Theor schrieb:>> Aber was ist mit der Regel das Vergleiche immer entweder zu 0 oder zu 1>> evaluieren. (Siehe das Zitat aus C99 hier:>> Beitrag "Re: C (MinGW): Unerwartete Bool-Auswertung")>> Arc N. schrieb:>> Nur darf der Compiler das nicht annehmen, sondern sollte sich an den>> Standard halten...>> Der Compiler trifft keine Annahmen, denn er ist kein Mensch. Er geht> schlichtweg davon aus, daß das, was er einmal reingeschrieben hat, auch> genauso zurück gelesen werden kann. Für die korrekte Funktion ist es> unerheblich, ob er beim Schreiben nach bool konvertiert oder erst beim> Lesen der bool-Variablen.>> Wenn einer am Compiler vorbei einen Bool falsch setzt, dann ist er auch> ganz allein dafür verantwortlich. Ursächlich für das Fehlverhalten ist> also nicht der Compiler, sondern der Programmierer mit seiner Annahme,> der Compiler würde überflüssige Konvertierungen einbauen.
Ich vermute, dass Du beide Zitate in einen Zusammenhang stellst, weil
von Standard die Rede ist.
Allerdings beziehen sich die beiden Zitate auf zwei verschiedene Aspekte
des Standards.
Einmal geht es darum, ob bool Werte >1 als identisch mit 1 behandelt
werden (Arc).
Ein andermal geht es darum, ob das Resultat von Vergleichen nicht
grundsätzlich 0 oder 1 sein muss. (Der TO verwendet ja dieses Resultat
als Operanden für den Schiebeoperator, nicht das bool selbst).
Dein Einwand beginnend mit "Wenn einer am Compiler vorbei ..." kann
sich, meiner Ansicht nach, auf das von Arc Gemeinte, aber nicht auf das
von mir Gemeinte beziehen.
Die Koexistenz beider Regeln, halte ich für problematisch, Und zwar
nicht, weil jemand einen Fehler gemacht hat, sondern weil, wie immer man
diese beiden Regeln gestaltet, ein Widerspruch entstehen kann.
Und zwar:
1. Die eine Regel erlaubt dem Implementierer, davon auszugehen, das bool
immer 0 oder 1 ist und das Vergleiche mit 0 und 1 resp. >1 redundant
sind.
2. Die andere Regel schreibt vor, dass Vergleiche immer Resultat 0 oder
1 haben.
Wenn aber die erste Regel nicht nur zur Code-Überprüfung (also zu
Warnungen oder Fehlermeldungen) verwendet wird, sondern zur Optimierung,
dann wird dabei potentiell (wie man in diesem Thread sieht) die zweite
Regel verletzt.
Ich denke dieser Widerspruch entsteht durch die Tatsache, dass bool
letztlich durch Speicher realisiert wird, der eben doch mehr als zwei
Zustände annehmen kann. Man kann in gewissen Fällen garnicht anders, als
entweder implizit die erste oder die zweite Regel temporär zu
ignorieren.
Ob nun dieses bool >1 explizit hingeschrieben wird oder funktional
entsteht (etwa durch externe Daten), beeinflusst nur die Tatsache ob der
Compiler das erkennen kann. Nicht aber seine Entscheidung, dass in der
gewählten Weise zu optimieren.
Letztlich musste man sich entscheiden, ob man diese Stellen nun
unabhängig von der zweiten Regel optimiert. (Wenn etwa, das Resultat
kein Teilausdruck mit letztlich arithmetischem Resultat ist, ist das ja
immer zulässig; etwa in if-Bedingungen).
Ich bin, wie ich schon schrieb, aus pragmatischen Gründen geneigt,
diesen Widerspruch zu akzeptieren, weil sonst im resultierenden Code
alle bool zunächst auf 0 oder 1 abgebildet werden müssten. Aber das das
nun völlig eindeutig so sein muss, will ich auch nicht bejahen.
Zum Schluss:
Eine Warnung, dass an solchen Stellen potentiell ein Problem entstehen
könnte (gibt es ja auch für andere Fälle, die nicht definitiv potentiell
ungewolltes Verhalten ergeben) halte ich für sinnvoll.
Ich schätze Dein Urteil im allgemeinen sehr, Peter, wenn ich auch hier
nicht Deiner Ansicht bin. Daher würde ich mich freuen, wenn Du das
kommentieren wolltest.
P.S. Im übrigen ist das Verhalten insgesamt gesehen auch nicht ganz
konsistent. Soweit mir bekannt ist (ich bitte ggf. um Richtigstellung)
gilt nämlich die Regel das >1 gleich true ist in if-Bedingungen auch für
bool.
P.P.S. Als alter Knopf verwende ich schlicht bool so gut wie gar nicht.
Daher kommt es wohl auch, dass ich überhaupt icht dazu neige,
vorauszusetzen, dass nur entweder 0 oder 1 auftritt. Entweder ich bin
durch Verwendung von Literalen sicher oder ich überprüfe das zur
Laufzeit, nachdem ich Daten eingelesen habe.
P.P.P.S
Beitrag "Re: C (MinGW): Unerwartete Bool-Auswertung"
Jörg schrieb:
> Ich habe mal dein "intmath.h" ersetzt durch:> ]...]
clang schafft es es bis Version 8 nicht, eine Warnung zu generieren.
GCC 5 ebenfalls nicht, GCC 8 wirft eine Warnung:
> ]...}
Es gibt also Licht am Ende des Tunnels. :)
Na Super. :-)
Oliver S. schrieb:> Damit dürfte es fast unmöglich sein, in einer Variablen vom Typ _Bool> was anderes als 0 oder 1 unterzubringen.
Es ist möglich mittels Type-Punning:
"If the member used to read the contents of a union object is not
the same as the member last used to store a value in the object, the
appropriate part of the object representation of the value is
reinterpreted as an object representation in the new type as described
in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a
trap representation."
"Trap representation" ist der Begriff der diese "unmöglichen" Werte
bezeichnet.
Theor schrieb:> Wenn aber die erste Regel nicht nur zur Code-Überprüfung (also zu> Warnungen oder Fehlermeldungen) verwendet wird, sondern zur Optimierung,> dann wird dabei potentiell (wie man in diesem Thread sieht) die zweite> Regel verletzt.
Die Regeln gelten aber nur, wenn es ein fehlerfreies Programm gibt. Wenn
in einem bool aber etwas anderes als 0 oder 1 steht, ist das Programm
nicht mehr fehlerfrei. Der Standard ist sehr eindeutig in Bezug auf
undefined behaviour.
Mein Nachtrag zum original Thread, bzw. meine Ergänzung zum dortigen
Beitrag
1
Autor: Bernd K. (prof7bit)
2
Datum: 16.09.2019 15:39
Die genaue Definition von trap representation aus dem Standard
(6.2.6.1.5 aus N1256):
1
Certain object representations need not represent a value of the object type. If the stored
2
value of an object has such a representation and is read by an lvalue expression that does
3
not have character type, the behavior is undefined. If such a representation is produced
4
by a side effect that modifies all or any part of the object by an lvalue expression that
5
does not have character type, the behavior is undefined.41) Such a representation is called a trap representation.
Hm. Offenbar liegt meiner Frage die folgende falsche Annahme zugrunde:
"Falls in in einem komplexen Ausdruck, ein Teilausdruck die Bedingungen
für 'undefined behavior" erfüllt, so muss dieses Verhalten durch in der
Auswertungsreihenfolge nachfolgende Ausdrücke aufgehoben (also wieder
definiert) werden.
Das ist natürlich nicht zwangsläufig so und im Standard auch nicht
gesagt.
Keine Ahnung wie ich darauf kam. Naja. Errare humanum est. :-)
OK. Danke.
Ich verstehe Dein Problem nicht. Der Compiler arbeitet genau nach
Standard. Jeder Ausdruck != 0 wird als true ausgewertet. Wird ein
Ausdruck einem Bool zugewiesen, speichert der Compiler 0 oder 1. Er geht
daher davon aus, daß er vorher 0 oder 1 gespeichert hat.
Probleme gibt es nur in folgenden Fällen, die alle Programmierfehler
sind:
- Der Bool wird gelesen, bevor ihm was zugewiesen wurde.
- Per (void*) sagt man dem Compiler, halt die Klappe und mache was ich
sage, auch wenn es falsch ist.
- Eine Union aus bool und int, auf die gegenseitig zugegriffen wird.
ausgewertet.
Da r dem Standard gemäß nur true = 1 oder false = 0 sein kann, darf der
Kompiler diesen Vergleich durch r ersetzen. So weit, so gut.
Der Typ dieses Zwischenresultats ist m.M.n. bool (bzw. _BOOL).
Da nun r, falls es nicht oder fehlerhaft initialisert wurde keinen
Wert der ein bool repräsentieren darf, enthält, sondern einen anderen
(sagen wir z.B. (15) enthält, steht dort nach der Auswertung offenbar
sowas wie:
1
15<<2
Dieser Schritt enthält ein implizites cast von bool nach int.
In diesem Fall ein cast einer ungültigen Repräsentation eines bools zu
einem int. OK?
Ich habe nun, - wie ich jetzt meine, irrigerweise -, angenommen, der
cast sollte wenigstens so realisiert sein, dass er auch ungültige
Repräsentation in gültige umwandelt, bevor er die letzliche Umwandlung
in den Zieltyp int vornimmt.
Anders gesagt, habe ich angenommen, dass solche impliziten cast so
gestaltet sind oder sein sollten, dass aus undefined behavior wieder
defined behavior wird. Das war aber mein Irrtum.
Der Standard bestimmt hier ausdrücklich "undefined behavior". Er sagt
nicht , dass dieses undefined behavior "falls zweifelsfrei möglich"
(ich denke, dass das theoretisch möglich ist, ist unstreitig) durch
entsprechende Umwandlung sozusagen abgefangen werden muss.
Ich vermute, ich habe mich davon irreführen lassen, dass "man", etwa in
if-Bedingungen, 0 als false (also der else-Zweig wird ausgeführt) und
nicht-0 als true interpretiert und entsprechenden Code compiliert.
Irgendwie bin ich davon ausgegangen, dass dies implizit auch für solche
Ausdrücke gilt.
Habe ich mich verständlicher ausgedrückt?
Theor schrieb:> Ich vermute, ich habe mich davon irreführen lassen, dass "man", etwa in> if-Bedingungen, 0 als false (also der else-Zweig wird ausgeführt) und> nicht-0 als true interpretiert und entsprechenden Code compiliert.
Yep.
Der Punkt ist: die Bedingungen von if (etc.) haben den Typ "int", nicht
"_Bool". Daher muss der Compiler an dieser Stelle explizit einen Test
auf !=0 vornehmen. Hat er dagegen schon ein _Bool vorliegen, kann er
implizit annehmen, dass dieses nur entweder 0 oder 1 sein kann. Damit
kann er natürlich auch in einem if, wenn der Operand vom Typ _Bool ist
(aber nur dann), einen Test auf ==1 statt !=0 einbauen.
Bernd K. schrieb:> Es ist möglich mittels Type-Punning:
Es geht auch mit den weiter oben schon angedeuteten
Pointer-cast-Schweinereien. Letztendlich ist das immer noch C ;)
Wenn’s dann dem Programmierer auf die Füße fällt, ist der halt selber
schuld.
Oliver
Jörg W. schrieb:> wenn der Operand vom Typ _Bool ist> (aber nur dann), einen Test auf ==1 statt !=0 einbauen.
…oder z. B. nur Bit 0 der Speicherstelle prüfen.
Alles schon gesehen.
Jörg W. schrieb:> Theor schrieb:>> Ich vermute, ich habe mich davon irreführen lassen, dass "man", etwa in>> if-Bedingungen, 0 als false (also der else-Zweig wird ausgeführt) und>> nicht-0 als true interpretiert und entsprechenden Code compiliert.>> Yep.>> Der Punkt ist: die Bedingungen von if (etc.) haben den Typ "int", nicht> "_Bool".> [...]
Der Ausdruck muss einen Skalar ergeben, wenn Du mir die Erbsenzählerei
nachsehen willst. Kann also, unter Anderem, int, aber eben auch _Bool
sein. :-)
Aber meine Frage ist ja nun erledigt. Danke für Eure freundliche Geduld.
Theor schrieb:>> Der Punkt ist: die Bedingungen von if (etc.) haben den Typ "int", nicht>> "_Bool". [...]>> Der Ausdruck muss einen Skalar ergeben, wenn Du mir die Erbsenzählerei> nachsehen willst.
Na gut, überredet. :-)