Der Compiler mahnt in der markierten Zeile an: "warning: 'bits' may be
used uninitialized in this function [-Wmaybe-uninitialized]"
Meine naive Denkweise wäre: "Mit der Initialisierung bit=0 ist die
Bedingung "if( !(bit++ & 7) )" beim ersten Durchlauf der inneren
Schleife grundsätzlich immer erfüllt und damit bits initialisiert. Und
der Compiler müßte das genauso sehen."
Aber er sieht das irgendwie anders als ich? Wo ist mein Denkfehler?
Viele Grüße
W.T.
Hi,
Die Variable "bit" ist initialisiert, "bits" aber nicht. Dass "bits"
später konditional ein Wert zugewiesen wird, wird für Warnung nicht
berücksichtigt. Aber deshalb heißt es ja auch nur "may be
uninitialized". Übrigens tut man in solchen Fällen nicht nur dem
Compiler einen Gefallen, wenn man die Variable einfach initialisiert,
auch wenn sie anschließend noch mal überschrieben wird. Es ist einfach
besser lesbar.
>Meine naive Denkweise wäre: "Mit der Initialisierung bit=0 ist die>Bedingung "if( !(bit++ & 7) )" beim ersten Durchlauf der inneren>Schleife grundsätzlich immer erfüllt und damit bits initialisiert.
Und was kostet es dich einmal zu schreiben bits = 0;?
Compiler zufrieden. Dafür so einen Aufriss hier?
Sonst nix besseres zu tun?
Hallo Felix und Johann,
danke für die Antworten.
Felix U. schrieb:> Dass "bits"> später konditional ein Wert zugewiesen wird, wird für Warnung nicht> berücksichtigt.
Das ist der Punkt: Ich bin immer wieder überrascht, welche Annahmen der
optimierende Compiler bei seinem Blick durchs Schlüsselloch treffen
kann. Da wundert es es auf der anderen Seite schon, daß eine für den
menschlichen Leser offensichtliche Bedingung nicht erkannt wird.
Das kann natürlich auch einfach daran liegen, daß die Warnungen in einer
anderen Compilerstufe als die Optimierungen erzeugt werden.
Felix U. schrieb:> [...] wenn man die Variable einfach initialisiert,> auch wenn sie anschließend noch mal überschrieben wird. Es ist einfach> besser lesbar.
Als Autor dieses Quelltext war meine Absicht, die bewußte
Nicht-Initialisierung dem Quelltext-Leser (meistenteils mir) als Wink
mit dem Zahnpfahl mitzugeben, daß ich annehme, daß die Bedingung der
inneren Schleife im ersten Schleifendurchlauf grundsätzlich immer
erfüllt ist.
Daß zwei fortgeschrittene Programmierer (zumindest bei einem bin ich mir
sicher) gegenteiliger Ansicht sind, gibt mir zu denken. Vielleicht
ergibt sich mit einer Woche Distanz zum eigenen Quelltext auch ein ander
Blick.
Viele Grüße
W.T.
Walter T. schrieb:> Als Autor dieses Quelltext war meine Absicht, die bewußte> Nicht-Initialisierung dem Quelltext-Leser (meistenteils mir) als Wink> mit dem Zahnpfahl mitzugeben, daß ich annehme, daß die Bedingung der> inneren Schleife im ersten Schleifendurchlauf grundsätzlich immer> erfüllt ist.
wenn du das annimmst, warum machst du die zuweisung dann konditionell?
1
if( !(bit++ & 7) )
2
{
3
bits = *bitmap++;
4
}
5
6
if( bits & 0x80 ) // <== warning 'bits' may be used uninitialized
lass doch "if( !(bit++ & 7)" einfach weg und schreibe nur "bits =
*bitmap++;"
alfred schrieb:> lass doch "if( !(bit++ & 7)" einfach weg und schreibe nur "bits => *bitmap++;"
Und Du meinst, dass das Programm dann noch dasselbe macht?
Walter T. schrieb:> Als Autor dieses Quelltext war meine Absicht, die bewußte> Nicht-Initialisierung dem Quelltext-Leser (meistenteils mir) als Wink> mit dem Zahnpfahl mitzugeben, daß ich annehme, daß die Bedingung der> inneren Schleife im ersten Schleifendurchlauf grundsätzlich immer> erfüllt ist.
Das erste Unangenehme ist, daß solche Kunstwerke bei der nächsten
Erweiterung des Quelltextes spontan zerbrechen, wenn man diese
impliziten Abhängigkeiten nach ein paar Monaten nicht mehr exakt im Kopf
hat. Noch fieser, wenn jemand anderes diese Erweiterungen machen soll.
Es ist also zumindest ein Wartbarkeitsproblem.
Das Zweite ist, selbst wenn die GCC-Warnung im konkreten Fall
unberechtigt ist, sollte man den Code trotzdem so korrigieren, daß die
Warnung verschwindet. Ziel sollte immer sein, den Code mit 0 Warnungen
durchlaufen zu lassen, auch wenn -Wall und -Wextra aktiviert werden. Am
besten auch gleich -Werror, damit Warnungen garantiert nicht ignoriert
werden.
Wenn man das nicht von Anfang an so macht, hat man irgendwann hunderte
Warnungen, die alle unberechtigt sind - und wie soll man dann die ein
oder zwei darin sehen, die auf einen tatsächlichen Fehler verweisen.
Walter T. schrieb:> Wo ist mein Denkfehler?
Dass du denkst, du musst Variablen nicht mit einem Default-Value
initialisieren, weil es die tausend anderen Arduino-Kiddies auch nicht
machen.
Google mal nach 'Defensive programming'
Viel schlimmer finde ich hingegen, dass du den bitmap-Pointer nicht auf
Gültigkeit abprüfst. Du stehst wohl auf Überraschungen und Exceptions
zur Laufzeit?
mfg
Felix F. schrieb:> Dass du denkst, du musst Variablen nicht mit einem Default-Value> initialisieren, weil es die tausend anderen Arduino-Kiddies auch nicht> machen.
Auch eine Sache, die mich immer wieder erstaunt: Wenn man offen zugibt,
daß es Menschen gibt, die besser programmieren können als man selbst,
glauben plötzlich alle anderen, auch zu dieser Gruppe zu gehören.
Nop schrieb:> [...] Ziel sollte immer sein, den Code mit 0 Warnungen> durchlaufen zu lassen, [...]
Wenn das nicht schon im Vornherein klar wäre - warum sollte man so eine
Frage stellen?
Felix F. schrieb:> Du stehst wohl auf Überraschungen und Exceptions> zur Laufzeit?
Nein, aber ich kenne mehr vom Quelltext als nur diese eine Funktion.
Felix F. schrieb:> dass du den bitmap-Pointer nicht auf> Gültigkeit abprüfst. Du stehst wohl auf Überraschungen und Exceptions> zur Laufzeit?
das bläht aber den Code auf, machts noch weniger leserlich und kratzt
u.U. schon an der flash und SRAM Größe
klar besser ist das alles immer vorher zu überprüfen, das kostet halt
Zeit und Resourcen und verhindert schnelle Erfolge.
Ich kümmere mich lieber um Probleme wenn sie auftauchen, aber es ist nur
Hobby (komischerweise sehen das die Profientwickler ja ähnlich, sonst
gäbe es keine Updates oder Rückrufe)
Joachim B. schrieb:> Ich kümmere mich lieber um Probleme wenn sie auftauchen
Erzähl das mal den Familien der 200 Toten, weil du im Autopilot ein
if(pointer != 0) vergessen hast ;)
Walter T. schrieb:> Auch eine Sache, die mich immer wieder erstaunt: Wenn man offen zugibt,> daß es Menschen gibt, die besser programmieren können als man selbst,> glauben plötzlich alle anderen, auch zu dieser Gruppe zu gehören.
Ich halte mich nicht für einen Experten, ich versuche bloß die Standards
einzuhalten. Wenn das schon zu viel für dich ist, ist es nicht mein
Problem.
mfg
Felix F. schrieb:> Viel schlimmer finde ich hingegen, dass du den bitmap-Pointer nicht auf> Gültigkeit abprüfst
Das geht sowieso nur sehr begrenzt, ein Pointer der kein Nullpointer ist
kann auch ungültig sein... ein assert() wäre vermutlich angemessen.
An W.T.:
Stell dir mal vor du hast in einer Funktion einen Simulator einer Turing
Maschine. Am Anfang wird eine Variable deklariert, aber nicht
initialisiert. Wenn der Simulator feststellt dass das Programm der TM
terminiert, liest er die Variable aus. Der Compiler soll also eine
Warnung ausgeben, genau dann wenn der Algorithmus terminiert. Mit
anderen Worten, soll der Compiler das Halteproblem lösen. Das ist
unmöglich. Somit kann der beste Compiler der Welt diese Warnung nicht
100% akkurat ausgeben. Deswegen steht da ja auch "may".
Ansonsten kann ich nur "Nop" absolut recht geben. Während der
Entwicklungs Phase sollte -Werror Pflicht sein! Hab schon öfter gesehen
dass Leute Fehler nicht gesehen haben weil die Warnung zwischen anderen
Warnungen untergeht...
Walter T. schrieb:>> Als Autor dieses Quelltext war meine Absicht, die bewußte> Nicht-Initialisierung dem Quelltext-Leser (meistenteils mir) als Wink> mit dem Zahnpfahl mitzugeben, daß ich annehme, daß die Bedingung der> inneren Schleife im ersten Schleifendurchlauf grundsätzlich immer> erfüllt ist.>
Für genau solche Zwecke gibt es Kommentare.
Felix U. schrieb:> Übrigens tut man in solchen Fällen nicht nur dem> Compiler einen Gefallen, wenn man die Variable einfach initialisiert,> auch wenn sie anschließend noch mal überschrieben wird. Es ist einfach> besser lesbar.
In diesem Fall ist es tatsächlich einfach, mit dem richtigen Wert zu
initialisieren. Bei der folgenden Abfrage zum Update von (bits) wird der
Post- zum Prefix Increment.
"Einfach" dagegen mit einem Default initialisieren, um den Compiler
glücklich zu machen, wäre dagegen kontraproduktiv. Ich bin kein Fan von
vielen Kommentaren, im letzteren Fall wäre dann imho aber einer
angebracht.
Dr. Sommer schrieb:> Stell dir mal vor du hast in einer Funktion einen Simulator einer Turing> Maschine.
Meines Wissens nach ist die C Virtual Machine keine Turing-Maschine, das
Problem, ob die Variable "bits" initialisiert ist, ist wohldefiniert und
vom Halteproblem nicht betroffen.
Vom Standard her wäre es völlig legitim, wenn für jeden Aufruf der
Funktion die kompletten Schleifen abgerollt würden. Dann gäbe es die
erste if-Bedingung in der Schleife nicht mehr.
Es handelt sich lediglich um eine Designentscheidung der Compilerbauer,
wie tief die Codeanalyse getrieben wird, bevor eine Warnung ausgegeben
wird.
Adapter schrieb:> Für genau solche Zwecke gibt es Kommentare.
Kommentare sind die zweitbeste Lösung, wenn der Quelltext für die
übersichtliche Darstellung meiner Absichten nicht ausreicht. Hier komme
ich nicht drum herum. Aber ich halte es trotzdem sinnvoll zu klären, ob
es nötig ist. Das ist geschehen.
Walter T. schrieb:> Meines Wissens nach ist die C Virtual Machine keine Turing-Maschine,
Das spielt keine Rolle. C ist Turing Vollständig, man kann
Turing-Maschinen simulieren.
Walter T. schrieb:> das> Problem, ob die Variable "bits" initialisiert ist, ist wohldefiniert und> vom Halteproblem nicht betroffen.
Doch. Wie gesagt, wenn der Compiler für jeden beliebigen Algorithmus
bestimmen könnte, ob eine Variable uninitialisiert benutzt wurde, könnte
er wie oben begründet das Halte-Problem lösen. Entweder sind also 100
Jahre Informatik falsch - oder die Annahme, dass es möglich ist, die
Warnung akkurat zu machen.
Die Warnung ist in vielen einfachen Fällen korrekt, aber eben nicht in
allen. Man könnte höchstens darüber diskutieren, ob der Compiler mehr
Fälle erkennt.
Dr. Sommer schrieb:> Wie gesagt, wenn der Compiler für jeden beliebigen Algorithmus> bestimmen könnte,
Es ist nicht jeder beliebiger Algorithmus. Er ist nicht rekursiv. Er ist
noch nicht einmal iterativ. Es ist eine popelige Vorwärtsschleife, bei
der die exakte Anzahl der Schleifendurchläufe schon zur Laufzeit
feststeht. Bei genauerer statischer Codeanalyses sogar zur Compilezeit.
Bei nicht so genauer Analyse ist aber immerhin sicher, daß sie kleiner
als INT_FAST16_MAX * INT_FAST16_MAX ist.
Man könnte sich diese ganze Diskussion auch sparen in dem man:
1. Die Variable gleich korrekt initialisiert: uint8 bits = *bitmap++;
2. Sich somit die if-Abfrage in der Schleife spart -> Performance
3. Am Schluss der Schleife ein bits = *bitmap++; macht.
4. Die Variable komplett sparen und direkt auf bitmap zugreifen.
mfg
Felix F. schrieb:> 1. Die Variable gleich korrekt initialisiert: uint8 bits = *bitmap++;
Nicht sinnvoll: Der Pointer bitmap verschiebt sich dann um eine Stelle
vor dem Eintritt in die beiden inneinander verschachtetelten Schleifen.
> 2. Sich somit die if-Abfrage in der Schleife spart -> Performance
Ohne if macht das Programm nicht mehr dasselbe: Das if prüft nämlich, ob
die unteren drei bits in der Variablen bit gesetzt sind. Die Zuweisung
1
bits=*bitmap++;
wird durchgführt für bit = 0, 1, 2, 3, 4, 5, 6 und NICHT für 7, 15 usw.
> 3. Am Schluss der Schleife ein bits = *bitmap++; macht.
Und Du meinst, dass das Programm dann noch dasselbe macht?
> 4. Die Variable komplett sparen und direkt auf bitmap zugreifen.
Interessante These, schau mal nach dem Bitshift bits <<= 1 weiter unten.
Deine Änderungen bewirken lediglich, dass die Funktion letztendlich was
ganz anderes macht.
Walter T. schrieb:> Es ist nicht jeder beliebiger Algorithmus.
Ja, wie gesagt, für einfache Fälle kann man drüber diskutieren. Im
Allgemeinen ist das aber unmöglich.
Walter T. schrieb:> Er ist nicht rekursiv.
Sind Turing-Maschinen auch nicht :P
Felix F. schrieb:> Erzähl das mal den Familien der 200 Toten, weil du im Autopilot ein> if(pointer != 0) vergessen hast ;)
ne ne ne,
mein Ex-Chef meinte mal, manchmal muss man einen Entwickler die Teile
auch aus der Hand reissen weil die sonst nicht ausgeliefert werden.
Er ist wohl nicht alleine mit dieser Meinung
das passt irgendwie
https://www.n-tv.de/politik/Tornados-koennen-nicht-nachts-abheben-article16796836.html
Wenn alles nur schnell auf den Markt kommen soll, soll man sich auch
nicht über miese Software beschweren (Windows-Updates die das OS
zerschießen, Browser die viel RAM brauchen, Sicherheitslücken überall,
Unbenutzbarkeit alter Hardware mit neuer Software, usw)
Walter T. schrieb:> Kommentare sind die zweitbeste Lösung, wenn der Quelltext für die> übersichtliche Darstellung meiner Absichten nicht ausreicht.
Ok, den Leser erwartet ein bunter Mix aus Ratequiz und
Reverse-Engineering — überspitzt ausgedrückt.
Wenn Wissen über den Code vorhanden ist, warum das nicht hinschreiben?
Warum diese Wissen aus dem Code extrahieren müssen wenn es bereits
vorhanden ist? Wenn deine Kommentare freilich lauten:
// Initialize x with 2.
x = 2;
dann taugen sie vielleicht für ein C-Tutorial, nicht aber für sinnvolle
Darstellung dessen, warum etwas gemacht wird, warum ein bestimmter,
womöglich (scheinbar) offensichtlicher Ansatz nicht gewählt oder
wieder verworfen wurde, oder als Zusammenfassung der nächsten
Arbeitsschritte.
// Just an arbitrary value, determined by fair dice roll.
// Very init will happen at gaga below.
x = 2;
Walter T. schrieb:> Meines Wissens nach ist die C Virtual Machine keine Turing-Maschine,
C ist Turing-vollständig.
> Vom Standard her wäre es völlig legitim, wenn für jeden Aufruf der> Funktion die kompletten Schleifen abgerollt würden. Dann gäbe es die> erste if-Bedingung in der Schleife nicht mehr.>> Es handelt sich lediglich um eine Designentscheidung der Compilerbauer,> wie tief die Codeanalyse getrieben wird, bevor eine Warnung ausgegeben> wird.
ACK. Und eine Design-Regel von GCC ist: Verbrauch von Host-Resourcen
(Speicher, Zeit) ist linearistisch, d.h. in O(x^{1+eps}) mit eps > 0
beliebig.
Wenn man nun Schleifen schält oder abrollt, bleibt die Komplexität immer
noch lniearistisch wenn man z.B. die Schälung statisch durch 1000 nach
oben beschränkt. Dies ist eine Design-Entscheidung, die u.U. durch
Schalter oder Parameter beeinflussbar ist; die Doku sagt dir mehr.
I.d.R werden Optimierungspasses auch nicht ausgeführt, um statische
Analyse zu betreiben, sondern um den Zielcode zu optimieren. Was auch
bedeutet, dass das Ergebnis der Analyse etwa in Form von Warnungen
optimierungsabhängig sein kann — das schrieb ich bereits oben.
Wenn du also die Analyse verbessern willst, kannst du versuchen:
-funroll-loop -funroll-all-loops -fpeel-loops ...
Walter T. schrieb:> Es ist nicht jeder beliebiger Algorithmus. Er ist nicht rekursiv.> Er ist noch nicht einmal iterativ.
Eine simple Sicht auf den Code ist, dass er aus 3 Blöcken besteht:
Entry-Block, Schleifenbauch B und Exit-Block X. Mit diesen 3 Ecken
ergeben sich folgende Kanten:
E->B, B->B und B->X
Eine mögliche dynamische Entwicklung bzw. Codefluß ist dann etwa:
E->B->B->B->B->B->B->B->B->B->B->B->B->B->B->X
B wird also immer wieder ausgeführt mit unterschiedlichen Vorgängern (E
und B), die unterschiedliche Daten enthalten. Wenn du eine Iteration
darstellst, hat die die gleiche Gestalt. Natürlich sieht man in
Datenanalyse nach Schälung, dass B optimiert (bei gegebenen Algorithmen
und Value-Range Information, Scalar Evolution, etc.) — das schrieb ich
bereits oben.
Johann L. schrieb:> // Just an arbitrary value, determined by fair dice roll.> // Very init will happen at gaga below.> x = 2;
Gehört da nicht 3 hin? Oder gilt XKCD nicht mehr als Referenz?
Johann L. schrieb:> Ok, den Leser erwartet ein bunter Mix aus Ratequiz und> Reverse-Engineering — überspitzt ausgedrückt.>> Wenn Wissen über den Code vorhanden ist, warum das nicht hinschreiben?> Warum diese Wissen aus dem Code extrahieren müssen wenn es bereits> vorhanden ist?
Unter uns: Siehst Du oben im Quelltext ein Ratequiz? Die Funktion ist
nicht toll. Es ist ein Vorher-Stand aus einer Aufräumaktion. Es müßten
eigentlich zwei Funktionen sein. Es müßte auch auf den Nullzeiger
geprüft werden. Es ist auch nicht schön, daß bitlogisch auf eine dezimal
dargestellte Variable geprüft wird, obwohl eher das Bitmuster
interessant ist. (Prüfen auf Divisionsrest wäre sicherlich noch etwas
sprechender). Die Namen bit <-> bits sind auch nicht toll. Ich habe
diese Funktion hier genutzt, weil sie das Verhalten bei den Warnings
zeigt, was mich interessierte.
Aber ist es ein Ratequiz? Steckt da irgendeine nicht-offensichtliche
Designentscheidung drin, die mit einem Kommentar besser verständlich
wäre, als wenn einfach die Variablen und Konstanten passend benannt
werden?
Johann L. schrieb:> Wenn du also die Analyse verbessern willst, kannst du versuchen:> -funroll-loop -funroll-all-loops -fpeel-loops ...
Danke für den Tipp. Das probiere ich gleich mal aus. Wobei ich mir für
eine verbesserte Analyse mittlerweile ein virtuelles Linux mit
Clang/LLVM aufgesetzt habe.
Johann L. schrieb:> Eine mögliche dynamische Entwicklung bzw. Codefluß ist dann etwa:>> E->B->B->B->B->B->B->B->B->B->B->B->B->B->B->X
Den Fluß verstehe ich. Nur nicht, was Du damit ausdrücken willst.
Willst Du damit ausdrücken, daß aus Compilersicht der Unterschied
zwischen einer Schleife mit im vornherein feststehender Anzahl an
Durchläufen und einer iterativen Abbruchbedingung weniger Unterschied
besteht, als es von Nutzersicht aussieht? Oder daß bei der Optimierung
das eine in das andere überführt werden kann (in einer Richtung ist das
klar... aber in Gegenrichtung? ) ? Oder stehe ich auf dem Schlauch?