Hallo zusammen, man möchte ja nach Möglichkeit unnötige Initialisierungen lokaler Variablen vermeiden. Wenn ich aber nur dort initialisiere, wo es nach der Logik des Algorithmus nötig ist, bekomme ich vom gcc (der den Algorithmus natürlich nicht durchschauen kann) die Warnung, die Variable "may be used uninitialized". Ich häufe nicht gerne Warnungen an, damit ich neue, ernst zu nehmende Warnungen leichter entdecke. Daher meine Frage: habe ich eine Chance, ihm zu sagen, daß er mich (im jeweiligen Fall) nicht warnen muß oder brauche ich wirklich eine eigentlich überflüssige Initialisierung? Dank und Gruß, Philipp.
Zeig doch mal nen Fetzen Code, vielleicht ist die Warnung ja tatsächlich berechtigt...
> Wenn ich aber nur dort initialisiere, wo es nach > der Logik des Algorithmus nötig ist, bekomme ich vom gcc (der den > Algorithmus natürlich nicht durchschauen kann) die Warnung, die > Variable "may be used uninitialized". Normalerweise kommt diese Warnung nur, wenn du wirklich nach Definition der Variable diese ausliest, ohne sie vorher je beschrieben zu haben. Kannst du mal zeigen, wie dein Code aussieht? > Ich häufe nicht gerne Warnungen an, damit ich neue, ernst zu > nehmende Warnungen leichter entdecke. Gute Idee. > Daher meine Frage: habe ich eine Chance, ihm zu sagen, daß er mich > (im jeweiligen Fall) nicht warnen muß oder brauche ich wirklich > eine eigentlich überflüssige Initialisierung? Du kannst auch C99 verwenden. Da brauchst du die Variablen nicht alle am Anfang zu definieren, sondern kannst dies auch da tun, wo du einen Wert hast, mit dem du sie initialisieren kannst.
Man kann (fast) jeden Algorithmus so umformulieren bzw. implementieren, dass dieses Problem nicht auftritt. Wenn man eine Stelle im Code hat, wo der Compiler eben nicht erkennen kann, dass eine Variable vor der ersten Verwendung intialisiert wird, hat man eine Stelle im Code die "stinkt". ("Code smells", "Smelling Code"). Das ist eigentlich immer ein Anzeichen dafür, dass an diese Stelle irgendwann Probleme auftreten können. Darum: Bitte mal ein konkretes Beispiel. Aber kein künstliches, sondern konkret aus Deinem Programm. Nur keine Angst oder falsche Scham. Durch Kritik lernst Du zweimal. Erstens, Kritik zu ertragen, zweitens, aus Kritik einen Gewinn zu ziehen.
"Bitte mal ein konkretes Beispiel." Kein Problem, hier ist eins: http://www.mikrocontroller.net/forum/read-4-49709.html#new Und das sind dann die Warnungen: SCHEDULE.C: In function `timeradd': SCHEDULE.C:39: warning: `ipre' might be used uninitialized in this function SCHEDULE.C: In function `timerremove': SCHEDULE.C:82: warning: `irem' might be used uninitialized in this function Peter
In timeradd() besteht die Möglichkeit, daß die while-Schleife verlassen wird, ohne daß ein einziges Mal "ipre" ein Wert zugewiesen wird. Kurz vor Ende der Funktion wird "ipre" aber als Arrayindex verwendet; und da motzt der Compiler vollkommen zurecht. In timerremove() sieht das auch nicht besser aus. Der erste Zugriff auf irem ist ein lesender Zugriff und vorher fand keine Initialisierung statt. Auch hier motzt der Compiler vollkommen zurecht.
In der formalen Analyse von Programmstrukturen sind Compiler wie GCC den Programmierern oft überlegen. Er hat nur dann keine Chance, wenn bestimmte Bedingungen nicht auftreten können, also beispielsweise eine Variable immer einen bestimmten Wert hat, aber das nur der Programmierer, nicht aber der Compiler weiss. Wenn man meint, solchen Code partout verwenden zu müssen, dann kann man sicherlich auch mit -Wno-uninitialized leben.
@Rufus, "Auch hier motzt der Compiler vollkommen zurecht." er motzt an keiner Stelle zu recht. Es handelt sich da nämlich um sich gegenseitig ausschließende Fälle. Wenn die Zuweisung nicht erfolgt, dann wird der Ausdruck auch nicht verwendet. Es handelt sich um das Erzeugen einer verketteten Liste, wo auch der Sonderfall behandelt werden muß, daß es das erste Element in der Liste ist und demzufolge kein Vorgänger existiert. Daher ist es nutzlos, dem Vorgängerindex einen bestimmten Wert zuzuweisen. Ich gestehe es dem Compiler aber zu, daß er das nicht erkennen kann und deshalb lieber warnt. Er ist sich ja auch seiner Unzulänglichkeit bewußt und warnt eben nur. Peter
Jaja, keine Panik, Beispiel kommt ja schon! Bin doch nicht mehr der jüngste ... (-; Ich kürze das mal heftig, sonst kapiert niemand, was da los ist. Dieses Beispiel ist aus einer Routine, die Text auf ein GLCD schreibt: while (Zeichen) { unsigned char Breite = 0; const unsigned char *BitmapZeiger; switch (Zeichen) { case FILL_LINE: while (x++ < 128) { DisplayWrite(DisplayRead() & Maske); } break; case SMALL_SPACE: BitmapZeiger = CharBismaps + SPACE_OFFSET; Breite = 2; break; case TAB: // Hier kommt jetzt noch ein Haufen Steuercodes für // verschiedene Zwecke (invertieren usw.) default: Offset = pgm_read_byte(CharOffsets + Zeichen - ' ); Breite = 4 + pgm_read_byte(CharOffsets + Zeichen + 1 - ' ') - Offset; BitmapZeiger = CharBitmaps + (((unsigned int) (Zeichen - ' ')) < 2) + Offset; } while (Breite--) { if (!Haelfte) DisplayWrite((DisplayRead() & Maske) | (pgm_read_byte(BitmapZeiger++) >> Zeile)); else DisplayWrite((DisplayRead() & Maske) | (pgm_read_byte(BitmapZeiger++) << (8 - Zeile))); x++; } DisplayWrite(DisplayRead() & Maske); x++; Zeichen = *(++Textzeiger); } (Den Kram mit der "Hälfte" muß man an dieser Stelle nicht verstehen; das brauche ich, wenn die Textzeile zwischen zwei Displayzeilen geteilt wird.) Entscheidend ist: ich gehe den String entlang und je nach Inhalt (Steuerzeichen oder druckbar) muß ich anders reagieren im switch. Wenn ich nachher die Bitmap eines Zeichens drucken will, setzte ich den Bitmapzeiger auf die entsprechende Stelle und ermittle die Breite. Wenn die Breite null bleibt, wird der Bitmapzeiger also gar nicht benutzt. gcc merkt das natürlich nicht. Wenn jetzt jemand mit Fehlervermeidung kommt: wenn ich versehentlich eine Breite setze, wo keine hingehörte, würde auch Quatsch herauskommen, wenn ich den Bitmapzeiger vorher initialisiere (und keine Warnung bekomme). Nützt also gar nichts. Und das ist nur ein Beispiel von vielen. Andere sind z.B. wenn ich in einer Protokollabarbeitung einen switch für den Zustand im Protokoll habe. In einen bestimmten Zustand komme ich nur rein, wenn ich vorher einen Wert aus dem Protokoll entnehmen konnte. Also muß ich das nicht initialisieren. gcc hat keine Chance. Fehler passieren bei so etwas nicht. So gerne ich mich belehren lasse, daß man die Aufgaben auch anders lösen und den Code so entstinken kann: der Code funktioniert seit Jahren und wird ohne Komplikationen erweitert. Ich fange nicht an, das neu zu machen. Vielleicht beim nächsten Mal. Hier will ich wirklich nur die Warnungen loswerden und hätte mir vorstellen können, daß es da eine Lösung gibt, denn zumindest mir begegnet die Situation immer wieder. Danke einstweilen für alle Antworten!
Was ist an einem const unsigned char *BitmapZeiger = NULL; denn soviel mehr Arbeit ?
@Peter: Kannst Du mir das von Dir gesagte (mit dem gegenseitigen Ausschluss) mal anhand Deiner Funktion timerremove zeigen? Ich zitiere die hier mal (nur der Formatierung halber sind Kommentare entfernt) bit timerremove( funcp func ){ uchar ipre; uchar irem; uchar ifol = t_first; t_ctrl_struct t_data *p; do{ if( ifol == T_LAST ) return 1; ipre = irem; irem = ifol; p = &t_ctrl_lst[irem]; ifol = p->next; }while( p->func != func ); p->next = T_FREE; if( irem == t_first ){ t_first = ifol; t_delay += t_ctrl_lst[ifol].delta; }else{ t_ctrl_lst[ipre].next = ifol; if( ifol != T_LAST ) t_ctrl_lst[ifol].delta += p->delta; } return 0; } Hier genügt es, sich die ersten Zeilen der do..while-Schleife anzusehen. Entweder wird die Funktion hier mit 'nem return beendet, oder aber ein Lesezugriff auf irem durchgeführt. Da eine do..while-Schleife mindestens einmal durchlaufen wird ("post check loop"), sehe ich hier nicht, wie da der Lesezugriff auf die nicht intialisierte Variable 'irem' umgangen werden kann.
> Hier genügt es, sich die ersten Zeilen der do..while-Schleife > anzusehen. Entweder wird die Funktion hier mit 'nem return beendet, > oder aber ein Lesezugriff auf irem durchgeführt. Der daraus gelesene Wert (in ipre) wird in diesem Falle allerdings nicht weiter benutzt. Die Warnung ist natürlich trotzdem korrekt, es ist für den Compiler keineswegs sofort ersichtlich, dass diese Benutzung eines nicht initialisierten Wertes in der Tat harmlos ist.
@Rufus, ich hab mal alles unwesentliche durch ... ersetzt:
1 | bit timerremove( funcp func ){ |
2 | uchar ipre; |
3 | uchar irem; |
4 | uchar ifol = t_first; |
5 | |
6 | do{ |
7 | ...
|
8 | ipre = irem; // Erstlauf: ipre unbestimmt |
9 | irem = ifol; // Erstlauf: irem = t_first |
10 | ...
|
11 | }while(...); |
12 | |
13 | if( irem == t_first ){ // immer true nach Erstlauf |
14 | t_first = ifol; // ipre nicht benutzt |
15 | t_delay += t_ctrl_lst[ifol].delta; // ipre nicht benutzt |
16 | }else{ // nie true nach Erstlauf |
17 | t_ctrl_lst[ipre].next = ifol; // ipre benutzt |
18 | ...
|
19 | }
|
20 | }
|
d.h. wird die Schleife nur einmal durchlaufen (das muß sie ja mindestens), dann ist irem == t_first und damit wird der else Zweig, wo ipre verwendet wird, nicht ausgeführt. @A.K. "In der formalen Analyse von Programmstrukturen sind Compiler wie GCC den Programmierern oft überlegen." Nun in diesem Fall ist es aber der Mensch. Erwarten tue ich es nicht, aber erstaunen würde es mich auch nicht, wenn ein Compiler sowas erkennen könnte. Aber solche Sachen, daß ich für Parameterübergaben nur bestimmte Werte erwarte, würde ich nie machen. Mich wundert es immer wieder, wenn Programme abstürzen, weil über die UART oder Tastatur unerwartete Werte reinkommen. Ist das nur Leichtsinn oder pure Faulheit ? Peter
Im Prinzip ist das genau das gleiche, wie bei Philip: Eine Variable wird zu Anfang so gesetzt, daß die Bedingung zum Verwenden einer anderen uninitialisierten Variablen nicht erfüllt ist. Und erst wenn die andere Variable auf einen Wert gesetzt wurde, wird die Bedingungsvariable so gesetzt, daß nun eine Verwendung erfolgt. Ein Mensch kann derartige logische Verknüpfungen nachvollziehen. Ich muß z.B. nicht wissen, wieviel Benzin im Tank ist, wenn ich das Auto stehen lassen will. Peter
@Jörg Wunsch: > Der daraus gelesene Wert (in ipre) wird in diesem Falle allerdings > nicht weiter benutzt. Das ist eben aus dem Code so nicht direkt ersichtlich. Dieser Code ist typisch schlechter Stil.
> Der daraus gelesene Wert (in ipre) wird in diesem Falle allerdings > nicht weiter benutzt. > > Die Warnung ist natürlich trotzdem korrekt, es ist für den > Compiler keineswegs sofort ersichtlich, dass diese Benutzung eines > nicht initialisierten Wertes in der Tat harmlos ist. Wenn man streng nach C-Norm vorgeht, nicht. Das Verhalten ist undefiniert, wenn man die Variable liest, ohne vorher was reingeschrieben zu haben, und zwar auch dann, wenn man den Wert nur in eine andere Variable kopiert und sonst nie was damit macht. Also ist der Code eigentlich fehlerhaft.
> Das Verhalten ist > undefiniert, wenn man die Variable liest, ohne vorher was > reingeschrieben zu haben, ... Zitat bitte?
@AK (habe das erst gelesen, nachdem ich meine Antwort abgeschickt hatte): > In der formalen Analyse von Programmstrukturen sind Compiler wie > GCC den Programmierern oft überlegen. Das stellt aber schon erhebliche KI-Anforderungen, das zu analysieren. Mit sowas soll sich kein Compilerprogrammierer aufhalten, nur um eine Warnung zu vermeiden. Ich überblicke es und könnte ihm (frei nach Hammer) sagen "Vertrau mir -- ich weiß, was ich nicht initialisiere" > Er hat nur dann keine Chance, wenn bestimmte Bedingungen nicht > auftreten können, also beispielsweise eine Variable immer einen > bestimmten Wert hat, aber das nur der Programmierer, nicht aber > der Compiler weiss. Es steht alles im Code und ist noch nicht einmal besonders komplex. Aber er müßte gezielt danach suchen und ein Compiler hat besseres zu tun! > Wenn man meint, solchen Code partout verwenden zu müssen, dann > kann man sicherlich auch mit -Wno-uninitialized leben. Das betrifft dann aber gleich alle. Es mag aber eines Tages wirklich eine versehentlich nicht initialisierte Variable geben: da will ich die Warnung. Deswegen hatte ich auf eine Lösung gehofft, die individuell für eine Variable ist. @Olaf: Natürlich initialisiere ich derzeit die Variablen überflüssigerweise. Ich möchte es aber vermeiden aus demselben Grund, weshalb man jede unnötige Initialisierung weglassen sollte.
> Mit sowas soll sich kein Compilerprogrammierer aufhalten, nur um > eine Warnung zu vermeiden. Der Compiler macht eine Codeflussanalyse, anhand derer er entscheidet, wie groß die tatsächliche Lebensdauer einzelner Variablen sein muss. Dabei fallen solche Dinge wie die Initialisierungswarnung ,,nebenbei'' ab. (Daher fallen sie eben auch nicht ab, wenn du mit -O0 compilierst.)
Richtig. Und wenn nun der Compiler feststellt, dass da Mist gebaut wurde mit unintialisierten Variablen, kann er z.B. nicht optimieren.
@Unbekannter > Und wenn nun der Compiler feststellt, dass da Mist gebaut wurde mit > unintialisierten Variablen, kann er z.B. nicht optimieren. Er stellt ja keine Mist fest, sonst würde er ja einen Error melden. Und zum Optimieren muß er nur wissen, wann die Variable das erste mal gesetzt und das letzte mal gelesen wird. Peter
@Peter: > Und zum Optimieren muß er nur wissen, wann die Variable das erste > mal gesetzt und das letzte mal gelesen wird. Für einen Optimierer ist es noch viel interessanter zu wissen, woher der Inhalt einer Variablen kommt. Stichwort: "Register Transfer Analysis" und "Data Path Analysis" Mit unintialisierten Variablen verhinderst Du solche Analysen und der Optimierer hat Pech.
> Für einen Optimierer ist es noch viel interessanter zu wissen, woher > der Inhalt einer Variablen kommt. Warum ? Das hat doch nur darauf Einfluß, welche Instruktion er nehmen muß (LD, MOV, IN, LPM). > Mit unintialisierten Variablen verhinderst Du solche Analysen und der > Optimierer hat Pech. Nun bei allen derartigen Fällen war der Code mit Initialisierung um gerade dieses überflüssige LDI länger. Also schadet es der Optimierung in keinster Weise. Peter
>> Das Verhalten ist undefiniert, wenn man die Variable liest, ohne >> vorher was reingeschrieben zu haben, ... > > Zitat bitte? Ok, stimmt wohl nicht, da es sich um einen unsigned char handelt. Für allen nicht-char-Typen gilt aber, was ich sagte. Hier das gewünschte Zitat aus ISO/IEC 9899:1999: ===================================================== 6.2.6.1: 5 Certain object representations need not represent a value of the object type. If the stored value of an object has such a representation and is read by an lvalue expression that does not have character type, the behavior is undefined. If such a representation is produced by a side effect that modifies all or any part of the object by an lvalue expression that does not have character type, the behavior is undefined.41) Such a representation is called a trap representation. 41) Thus, an automatic variable can be initialized to a trap representation without causing undefined behavior, but the value of the variable cannot be used until a proper value is stored in it. ===================================================== Das bedeutet, daß der zufällig in der uninitialisierten Variable stehende Wert eine "trap representation" sein könnte. Wenn dieser Wert benutzt ("used") wird -- und das Kopieren in eine andere Variable zählt bereits als "use" -- ist das Verhalten damit undefiniert.
Danke, ich hatte es nicht gefunden. Mir war bislang nicht untergekommen, dass auch nicht-float-Werte eine trap representation sein können. Die Ausnahme mit dem character type erscheint natürlich dann irgendwie unlogisch... Ich hätte eher erwartet, dass dann diese Forderung auf alle integer-Typen zutrifft (was ja Sinn hätte).
@Peter: > Das hat doch nur darauf Einfluß, welche Instruktion er nehmen muß > (LD, MOV, IN, LPM). Um dieses "woher" geht es nicht. Das ist pille-palle. Es geht, wie ich schon sagte, um die Analyse des Datenpfad. Da setzt der Optimierer beim umsortieren der Expressions und der Statements an.
> Die Ausnahme mit dem character type erscheint natürlich dann > irgendwie unlogisch... Ich hätte eher erwartet, dass dann diese > Forderung auf alle integer-Typen zutrifft (was ja Sinn hätte). Das sehe ich auch so. Alle Integers sind ja vollständig kodiert, d.h. jeder Wert ist gültig. Um ungültige Integers zu haben, müßte dann ja die Speicherbreite 17 bzw. 33 Bit betragen. Ich hab aber noch nie was von einer 17 oder 33 Bit Architektur gehört. Peter
Ein Beispiel für eine Rechnerarchitektur, wo so etwas Probleme bereiten kann. Nicht mehr ganz taufrisch, aber wer weiss was die Zukunft bringt: Der deutsche Grossrechner Telefunken TR440 von '70 hatte zusätzlich zu seinen 48 Bit Wortbreite noch 2 Bits "Typenkennung". Darin wurde z.B. zwischen Code und Daten unterschieden. Stand die falsche drin, gab's beim Zugriff einen Typenkennungsalarm.
> Um dieses "woher" geht es nicht. Das ist pille-palle. Es geht, > wie ich schon sagte, um die Analyse des Datenpfad. Da setzt > der Optimierer beim umsortieren der Expressions und der > Statements an. Wenn der Compiler das in diesem Fall tun würde, wäre es ja schön und gut. Dann würde er auch nicht warnen, sondern im Gegenteil selbst eine vorhandene Initialisierung weglassen, weil er feststellt, daß sie überflüssig ist. Er analysiert es aber nicht und der Code wird größer und langsamer, wenn man die Initialisierung schreibt, um die Warnung zu vermeiden. Schließlich blockiert die zu früh gesetzte Variable ja auch Speicher, wenn der Compiler nicht merkt, daß das nicht nötig wäre. Und ich bleibe dabei: da ist den Programmierern des Compilers kein Vorwurf draus zu machen, weil die Analyse solcher Zusammenhänge nicht einfach nebenbei passiert und die meisten Anwender eine solche Optimierung wohl ausschalten würden, um den Compiler nicht auszubremsen. Da fallen mir wahrlich wichtigere Dinge ein, die man vorher verbessern könnte! An dieser Stelle würde es ja reichen, wenn der Programmierer dem Compiler seine eigene Analyse mitteilen könnte. Womit wir wieder bei meiner Frage wären ... (-;
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.