Forum: Compiler & IDEs Warnungen vermeiden


von Philipp Sªsse (Gast)


Lesenswert?

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.

von Patrick D. (oldbug) Benutzerseite


Lesenswert?

Zeig doch mal nen Fetzen Code, vielleicht ist die Warnung ja tatsächlich
berechtigt...

von Rolf Magnus (Gast)


Lesenswert?

> 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.

von Unbekannter (Gast)


Lesenswert?

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.

von Peter Dannegger (Gast)


Lesenswert?

"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

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

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.

von A.K. (Gast)


Lesenswert?

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.

von Peter Dannegger (Gast)


Lesenswert?

@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

von Philipp Sªsse (Gast)


Lesenswert?

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!

von Olaf Stieleke (Gast)


Lesenswert?

Was ist an einem

const unsigned char *BitmapZeiger = NULL;

denn soviel mehr Arbeit ?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

@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.

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


Lesenswert?

> 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.

von Peter Dannegger (Gast)


Lesenswert?

@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

von Peter Dannegger (Gast)


Lesenswert?

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

von Unbekannter (Gast)


Lesenswert?

@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.

von Rolf Magnus (Gast)


Lesenswert?

> 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.

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


Lesenswert?

> Das Verhalten ist
> undefiniert, wenn man die Variable liest, ohne vorher was
> reingeschrieben zu haben, ...

Zitat bitte?

von Philipp Sªsse (Gast)


Lesenswert?

@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.

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


Lesenswert?

> 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.)

von Unbekannter (Gast)


Lesenswert?

Richtig.

Und wenn nun der Compiler feststellt, dass da Mist gebaut wurde mit
unintialisierten Variablen, kann er z.B. nicht optimieren.

von Peter D. (peda)


Lesenswert?

@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

von Unbekannter (Gast)


Lesenswert?

@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.

von Peter D. (peda)


Lesenswert?

> 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

von Rolf Magnus (Gast)


Lesenswert?

>> 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.

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


Lesenswert?

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).

von Unbekannter (Gast)


Lesenswert?

@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.

von Peter D. (peda)


Lesenswert?

> 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

von A.K. (Gast)


Lesenswert?

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.

von Philipp Sªsse (Gast)


Lesenswert?

> 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
Noch kein Account? Hier anmelden.