Forum: Compiler & IDEs Könnte der gcc mich evt. warnen?


von eagle user (Gast)


Lesenswert?

Servus!
1
void
2
get_foo (int what, int *foo)
3
{
4
   *foo = one_wire_read_eeprom (what);
5
   if (*foo < 42 || *foo >= 666) { // Muell im EEPROM?
6
      foo = 0;                     // ja
7
   }
8
}
Ja, Sterne sind teuer, aber den einen hätte ich schon noch spendiert. 
Dass die Hardware NULL und 0 nicht unterscheidet, ist ja o.k., aber so 
eine einzelne 0 ist doch kein Pointer? Da müsste es doch eine Warnung 
geben?

von Peter II (Gast)


Lesenswert?

eagle user schrieb:
> einzelne 0 ist doch kein Pointer?

in C schon.

C++ kennt dafür jetzt ein nullptr;

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

eagle user schrieb:
> aber so
> eine einzelne 0 ist doch kein Pointer?
Noe, die 0 selber nicht. Aber du sagst, dass der Pointer bitte auf die 
Adresse 0 zeigen soll. Was sollte denn die 0 von jeder anderen Zahl 
unterscheiden? Warum sollte der Pointer nicht darauf zeigen duerfen? 
Warum soll dich der Compiler da warnen?

eagle user schrieb:
> Dass die Hardware NULL und 0 nicht unterscheidet
Die Hardware kennt sowas wie NULL gar nicht.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

eagle user schrieb:
> Servus!
>
1
void
2
> get_foo (int what, int *foo)
3
> {
4
>    *foo = one_wire_read_eeprom (what);
5
>    if (*foo < 42 || *foo >= 666) { // Muell im EEPROM?
6
>       foo = 0;                     // ja
7
>    }
8
> }
> Ja, Sterne sind teuer, aber den einen hätte ich schon noch spendiert.
> Dass die Hardware NULL und 0 nicht unterscheidet, ist ja o.k., aber so
> eine einzelne 0 ist doch kein Pointer? Da müsste es doch eine Warnung
> geben?

In C++ wäre foo eine Referenz und würde Dir 5 Sterne sparen für den 
Preis eines Unds:
1
void get_foo( int what, int& foo )
2
{
3
    foo = one_wire_read_eeprom( what );
4
    if ( foo < 42 || foo >= 666 ) {  // Muell im EEPROM?
5
        foo = 0;                     // ja
6
    }
7
}

Der wesentliche Unterschied wäre, dass man in C++ ausdrücken kann, dass 
&foo nicht 0 sein kann.

mfg Torsten

von Peter II (Gast)


Lesenswert?

Torsten R. schrieb:
> Der wesentliche Unterschied wäre, dass man in C++ ausdrücken kann, dass
> &foo nicht 0 sein kann.

wie meinst du das?

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Peter II schrieb:
> Torsten R. schrieb:
>> Der wesentliche Unterschied wäre, dass man in C++ ausdrücken kann, dass
>> &foo nicht 0 sein kann.
>
> wie meinst du das?

Technisch gesehen, ist eine Referenz nichts anderes, als ein Zeiger. 
Allerdings mit der Semantik, nie 0 zu sein. Referenzen, müssen z.B. 
immer initialisert sein und können auch nicht geändert werden.

von Markus F. (mfro)


Lesenswert?

schreib's künftig so:
1
void
2
get_foo (int what, int *foo)
3
{
4
    int res;
5
6
    res = one_wire_read_eeprom (what);
7
    if (res < 42 || res >= 666) { // Muell im EEPROM?
8
        res = 0;                     // ja
9
    }
10
    *foo = res;
11
}

Dann hast Du deine Warnung.

von Peter II (Gast)


Lesenswert?

Markus F. schrieb:
> schreib's künftig so:

warum überhaupt der Zeiger?
1
int get_foo (int what)
2
{
3
    int res;
4
5
    res = one_wire_read_eeprom (what);
6
    if (res < 42 || res >= 666) { // Muell im EEPROM?
7
        res = 0;
8
    }
9
10
    return res;
11
}

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


Lesenswert?

Andere Variante: den Zeiger als unverändlich deklarieren.
1
void
2
get_foo (int what, int * const foo)
3
{
4
   *foo = one_wire_read_eeprom (what);
5
   if (*foo < 42 || *foo >= 666) { // Muell im EEPROM?
6
      foo = 0;                     // ja
7
   }
8
}

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Peter II schrieb:
> Markus F. schrieb:
>> schreib's künftig so:
>
> warum überhaupt der Zeiger?
>

Wahrscheinlich, weil es ein Beispiel ist ;-)

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Eine Warnung, wenn man den Pointer auf NULL bzw. 0 setzt, waere sogar 
kontraproduktiv. Dies ein voellig normaler Vorgang. Wenn man mit 
dynamischen Speicher arbeitet setzt man den Pointer nach der Freigabe 
des Speichers auf NULL/nullptr (what ever) um zu signalisieren, das der 
Pointer ungueltig ist. Ebenso geben Funktionen wie malloc (usw.) einen 
NULL-Pointer zurueck, wenn kein Speicher alokiert werden konnte.

von Rolf M. (rmagnus)


Lesenswert?

eagle user schrieb:
> Dass die Hardware NULL und 0 nicht unterscheidet, ist ja o.k., aber so
> eine einzelne 0 ist doch kein Pointer? Da müsste es doch eine Warnung
> geben?

0 ist eine gültige Nullzeigerkonstante. NULL ist nur ein Makro, das in C 
im Prinzip entweder schlicht als 0 oder als (void*)0 definiert sein 
muss.

Peter II schrieb:
> eagle user schrieb:
>> einzelne 0 ist doch kein Pointer?
>
> in C schon.
>
> C++ kennt dafür jetzt ein nullptr;

0 geht auch in C++. Aber nullptr ist zu bevorzugen.

von Nullmeridianvermesser (Gast)


Lesenswert?

Was ist eigentlich, wenn man tatsächlich die Adresse 0 meint? Das würden 
einem doch viele Bibliotheksfunktionen um die Ohren hauen?

von Dr. Sommer (Gast)


Lesenswert?

Kaj G. schrieb:
> Noe, die 0 selber nicht. Aber du sagst, dass der Pointer bitte auf die
> Adresse 0 zeigen soll.

Das stimmt nicht ganz. Man kann (nicht direkt ohne cast) keine 
beliebigen Zahlen in Pointer rein schreiben, denn der C Standard 
definiert auch gar nicht dass Zeiger intern eine Zahl sind die einer 
Adresse entsprechen. Wenn man aber ein 0 Literal zuweist, wird ein "null 
pointer " geschrieben, der aber gar nicht notwendigerweise durch 0 
dargestellt wird, sondern ggf. auch über irgendein anderes Bitmuster. 
Praktisch nutzen die meisten Plattformen/Compiler für Nullpointer die 
Zahl 0. Es wäre aber denkbar, dass man z.B. auf Cortex-M tatsächlich 
0xFFFFFFFF in den Pointer schreibt, denn Zugriffe darauf führen zum 
Programm Absturz, während die Adresse 0 durchaus eine gültige sein kann 
(z.B. erstes Byte im Flash); ich wüsste aber gerade keinen Compiler der 
das kann...

von Markus F. (mfro)


Lesenswert?

Dr. Sommer schrieb:
> Wenn man aber ein 0 Literal zuweist, wird ein "null
> pointer " geschrieben, der aber gar nicht notwendigerweise durch 0
> dargestellt wird, sondern ggf. auch über irgendein anderes Bitmuster.

Das kann auch ins Auge gehen. Seit (ich glaube) 6.x kann man mit gcc 
nicht mehr ohne weiteres per * (unsigned long *) 0 auf Adresse 0 
schreiben (bei m68k manchmal notwendig, wenn man initial pc und initial 
sp schreiben will um einen Reset vorzubereiten). Die neuere Version 
frisst den Code, tut aber nicht mehr, was sie früher mal tat - nämlich 
auf Adresse 0 schreiben.

Sondern ruft einfach abort() auf. Trotz -ffreestanding. Auf m68k extra 
blöd, weil abort einen trap #7 auslöst, auf dem man nicht unbedingt 
einen trap-handler hat.

Hat mich mal ein paar Stunden gekostet, bis ich 
-fno-delete-null-pointer-checks gefunden habe...

von Dr. Sommer (Gast)


Lesenswert?

Markus F. schrieb:
> Die neuere Version
> frisst den Code, tut aber nicht mehr, was sie früher mal tat - nämlich
> auf Adresse 0 schreiben.
Ja, das ist durch C explizit erlaubt - 0 auf einen Pointer zu schreiben 
bewirkt, dass da ein Nullpointer-Wert gespeichert wird. Den zu 
derefenzieren ist undefiniert, und da darf der Compiler alles tun, z.B. 
abort() aufrufen. Zugriffe auf beliebige Adressen sind in C nicht 
wirklich vorgesehen, der Trick mit Integer nach Pointer casten ist 
eigentlich verboten, und gerade bei der Adresse 0 geht's schief... Man 
könnte sich notfalls mit Inline Assembly behelfen.

von Carl D. (jcw2)


Lesenswert?

Nullmeridianvermesser schrieb:
> Was ist eigentlich, wenn man tatsächlich die Adresse 0 meint? Das würden
> einem doch viele Bibliotheksfunktionen um die Ohren hauen?

Weil diese Bibliotheksfunktionen die Adresse 0 als ungültig ansehen, was 
auf CPUs mit Paging-Einheit dadurch gehandhabt werden kann, daß die 
erste Speicherseite nicht gemapped wird, also immer zu Pagefault führt 
und der Pagefault-Handler die NULL-Page speziell behandelt.

Dem Compiler ist es aber egal ob die allererste Adresse oder irgendeine 
danach kommende benutzt wird.

Auf typischer Forums-Hardware (AVR) kann man per *(uint8_t*0) das 
Register R0 erreichen, was selten Sinn macht.

von Markus F. (mfro)


Lesenswert?

Dr. Sommer schrieb:
> Zugriffe auf beliebige Adressen sind in C nicht
> wirklich vorgesehen, der Trick mit Integer nach Pointer casten ist
> eigentlich verboten,

wer sollte mir das verbieten? Im Gegenteil, das ist explizit erlaubt:

ANSI C99 6.3.2.3.5: an integer may be converted to any pointer type.

nur mit der NULL geht's nicht:

ANSI C99 .3.2.3.3: An integer constant expression with the value 0, or 
such an expression cast to type void *, is called a null pointer 
constant.55) If a null pointer constant is converted to a pointer type, 
the resulting pointer, called a null pointer, is guaranteed to compare 
unequal to a pointer to any object or function.

von A. S. (Gast)


Lesenswert?

C ist eine recht liberale Sprache. Wenn Du nur mal so was bastelst, 
siehst Du im Debugger, was da passiert.

Wenn Du ernsthaft Code schreibst, hätten Compiler oder Lint gewarnt, 
selbst wenn Du das const vor foo vergessen hättest. Einfach, weil die 
Zeile sinnlos ist und die statische Codeanalyse das erkennt.

von (prx) A. K. (prx)


Lesenswert?

Bei den INMOS Transputern war der Adressraum mit Vorzeichen definiert 
und ging folglich von -32768 bis +32767 (T212) oder 
0x80000000..0x7FFFFFFF (T414). Die Adresse 0 lag also mitten im 
Adressraum, und beim 16-Bitter aufgrund problemlos verfügbarer 
RAM-Kapazität auch ganz real mitten im RAM.

Bei der Implementierung von C war eine korrekte Umsetzung des Umgangs 
mit 0/NULL folglich nicht trivial: Der Wert 0 kann eigentlich nicht für 
die Funktion "ungültiger Pointer" verwendet werden, int*ptr=0 weist dem 
Pointer nicht den Wert 0 zu, if(ptr!=0) vergleicht intern nicht mit 0 
und (void*)0 ist nicht das wonach es aussieht. Und was bei if((int)ptr) 
rauskommt...

: Bearbeitet durch User
von eagle user (Gast)


Lesenswert?

Danke für die vielen tröstenden Worte! Die Suche nach dem Schuldigen 
endet wie immer bei mir :(

Jörg W. schrieb:
> Andere Variante: den Zeiger als unverändlich deklarieren.
> void
> get_foo (int what, int * const foo)

daran sollte ich mich gewöhnen...

Torsten R. schrieb:
> Peter II schrieb:
>> Markus F. schrieb:
>>
>> warum überhaupt der Zeiger?
>
> Wahrscheinlich, weil es ein Beispiel ist ;-)

in Wirklichkeit macht die Funktion noch etwas mehr und liefert statt 
void einen Pointer auf was anderes. Ja, das sollten evt. zwei getrennte 
Funktionen sein.

Markus F. schrieb:
>> Zugriffe auf beliebige Adressen sind in C nicht
>> wirklich vorgesehen, der Trick mit Integer nach Pointer casten ist
>> eigentlich verboten,
>
> wer sollte mir das verbieten? Im Gegenteil, das ist explizit erlaubt:
>
> ANSI C99 6.3.2.3.5: an integer may be converted to any pointer type.

Wie würde man auf die Hardware zugreifen wenn das nicht erlaubt wäre? 
Hmm, eine struct, die per Linker Script auf die richtige Adresse gemappt 
wird, wäre sogar irgendwie elegant.

Aber der entscheidende Satz ist doch "may be converted to any pointer 
type". Und das passiert bei einer nackten Konstanten nicht. Also sollte 
es bei so einer Zuweisung wenigstens eine Warnung geben. Oder aus diesem 
Grund:

Achim S. schrieb:
> Wenn Du ernsthaft Code schreibst, hätten Compiler oder Lint gewarnt,
> selbst wenn Du das const vor foo vergessen hättest. Einfach, weil die
> Zeile sinnlos ist und die statische Codeanalyse das erkennt.

Hat der Compiler aber nicht. Richtig analysiert hat er es - und das 
komplette if-statement wegoptimiert. Aber der gcc kennt z.B. eine 
Warnung "statement with no effect", sowas hätte ich mir hier gewünscht. 
Und die Leute sagen, mit dem aktuellen gcc bekommst du Lint gratis...


A. K. schrieb:
> Bei den INMOS Transputern war der Adressraum mit Vorzeichen definiert
> und ging folglich von -32768 bis +32767 (T212) oder
> 0x80000000..0x7FFFFFFF (T414).

Also deswegen sind die ausgestorben, nix Meteorit... ;)


Dr. Sommer schrieb:
> Es wäre aber denkbar, dass man z.B. auf Cortex-M tatsächlich
> 0xFFFFFFFF in den Pointer schreibt, denn Zugriffe darauf führen zum
> Programm Absturz, während die Adresse 0 durchaus eine gültige sein kann
> (z.B. erstes Byte im Flash);

Ab dem Cortex M0+ kann man per MPU einen "Page Fault" erzeugen wenn auf 
Adresse 0 zugegriffen wird. Sehr empfehlenswert!

von Dr. Sommer (Gast)


Lesenswert?

Markus F. schrieb:
> ANSI C99 6.3.2.3.5: an integer may be converted to any pointer type.

Ja, aber dereferenzieren darfst du den nur, wenn der Integer entstanden 
ist durch einen Pointer to Integer cast von einem "kompatiblen" Pointer 
(z.B. gleich oder char), und der Integer groß genug ist. Wie gesagt, C 
kennt keine Adressen. Es könnte auch wie in Java umgesetzt sein, wo 
Referenzen Objekt-Nummern enthalten. C ist halt doch kein 
Super-Assembler, sondern definiert ein plattformunabhängiges Verhalten . 
Ich meine es gibt Compiler (für AVR?) die in Pointern auch die Art des 
Ziel Speichers ablegen (Flash/RAM). Also keine einfache Adresse mehr.

eagle user schrieb:
> Ab dem Cortex M0+ kann man per MPU einen "Page Fault" erzeugen wenn auf
> Adresse 0 zugegriffen wird

Ok, da steht der initiale Stack Pointer, auf den muss man normalerweise 
nicht zugreifen...

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

eagle user schrieb:
> Aber der gcc kennt z.B. eine
> Warnung "statement with no effect", sowas hätte ich mir hier gewünscht.
Kleiner Tipp:
Nicht nur alleine auf den Compiler verlassen, sondern auch Analyse-Tools 
wie cppcheck & Co. nutzen. :)

von Rolf M. (rmagnus)


Lesenswert?

eagle user schrieb:
> Aber der entscheidende Satz ist doch "may be converted to any pointer
> type". Und das passiert bei einer nackten Konstanten nicht.
> Also sollte es bei so einer Zuweisung wenigstens eine Warnung geben.

Gerade bei der 0 nicht. Die hat hier eine Sonderrolle, weil sie im 
Zeigerkontext für einen Nulzeiger steht. Hättest du z.B. stattdessen foo 
= 3 (oder jede beliebige andere Zahl außer ausgerechnet 0) geschrieben, 
hättest du deine Warnung bekommen ("warning: assignment makes pointer 
from integer without a cast").

> Oder aus diesem Grund:
>
> Achim S. schrieb:
>> Wenn Du ernsthaft Code schreibst, hätten Compiler oder Lint gewarnt,
>> selbst wenn Du das const vor foo vergessen hättest. Einfach, weil die
>> Zeile sinnlos ist und die statische Codeanalyse das erkennt.
>
> Hat der Compiler aber nicht. Richtig analysiert hat er es - und das
> komplette if-statement wegoptimiert. Aber der gcc kennt z.B. eine
> Warnung "statement with no effect", sowas hätte ich mir hier gewünscht.

Einen Effekt hat das Statement ja schon, nur wird dieser Effekt nie 
genutzt (es wird ein Wert reingeschrieben, der danach nie verwendet 
wird). Es scheint, dass der gcc nur warnt, wenn eine Variable mal 
initial mit einem Wert belegt und danach nie benutzt wird ("warning: 
variable ‘foo’ set but not used"), aber nicht, wenn sie schon verwendet 
wurde und ihr am Schluss dann ein neuer Wert zugewiesen wird, der dann 
nicht mehr benutzt wird.

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Einen Effekt hat das Statement ja schon,

nein, hat es nicht.

foo ist eine lokale variable (also i.d.R. auf dem Stack), by value 
übergeben. Ein Schreiben auf foo hätte nur dann einen Sinn, wenn sie 
danach nochmal verwendet würde. Das ist hier nicht der Fall.

eagle user schrieb:
> Und die Leute sagen, mit dem aktuellen gcc bekommst du Lint gratis.

ja, natürlich steckt hinter gcc mehr manpower als hinter PC-Lint, so 
dass gcc immer besser wird. Aber zum einen ist Lint m.E. noch immer 
besser individualisierbar, zum anderen für jede Plattform verwendbar.

von Rolf M. (rmagnus)


Lesenswert?

Achim S. schrieb:
> Rolf M. schrieb:
>> Einen Effekt hat das Statement ja schon,
>
> nein, hat es nicht.

Doch. Der Effekt ist, dass foo den Wert 0 bekommt. Was danach damit 
passiert, ist dafür erstmal egal. Ein "statement with no effect" wäre 
sowas wie:
1
3;

> foo ist eine lokale variable (also i.d.R. auf dem Stack), by value
> übergeben. Ein Schreiben auf foo hätte nur dann einen Sinn, wenn sie
> danach nochmal verwendet würde. Das ist hier nicht der Fall.

Richtig. Da wäre aber eher so eine Warnung wie das oben schon erwähnte 
"variable ‘foo’ set but not used" angebracht.

: Bearbeitet durch User
von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
> Der Effekt ist, dass foo den Wert 0 bekommt

Naja, aber es hat keinen Einfluss auf den Programmverlauf.

Der Compiler darf auch ohne Optimierung das ganze if wegschmeißen, da 
foo by value übergeben ist und *foo nicht volatile.

Hat eine Anweisung if(1==0){} auch einen Effekt? Muss dann ein Vergleich 
stattfinden?

von Rolf M. (rmagnus)


Lesenswert?

Achim S. schrieb:
> Rolf M. schrieb:
>> Der Effekt ist, dass foo den Wert 0 bekommt
>
> Naja, aber es hat keinen Einfluss auf den Programmverlauf.

Richtig. Das ist aber dann Sache des Optimizers. Hier muss man auch 
unterscheiden zwischen dem, was im Code steht und dem, was der Compiler 
dann daraus macht. Auf C-Ebene bekommt foo erstmal den Wert 0, und damit 
ist ein Effekt da. So ist im Kontext dieser Warnung zumindest der 
Begriff "effect" zu verstehen.
Gemeint ist nicht, ob es für den weiteren Programmablauf eine Rolle 
spielt, sondern ob das Statement selbst auf C-Ebene überhaupt irgendwas 
tut.
Wenn du ganz ohne den weiteren Kontext nur die Zeile
1
foo = 0;
sehen würdest, würdest du da auch sagen, dass das keinen Effekt hat? 
Oder würdest du sagen: "Damit gebe ich foo den Wert 0"?

> Der Compiler darf auch ohne Optimierung das ganze if wegschmeißen, da
> foo by value übergeben ist und *foo nicht volatile.

Wieso "ohne Optimierung"? Wenn er das wegschmeißt, ist das ganz klar 
eine Optimierung.

> Hat eine Anweisung if(1==0){} auch einen Effekt? Muss dann ein Vergleich
> stattfinden?

Gute Frage. Das ist wohl Ansichtssache.

von Walter S. (avatar)


Lesenswert?

eagle user schrieb:
> foo = 0;                     // ja

eigentlich könnte in dem Fall der Compiler doch erkennen dass das eine 
Anweisung ohne Effekt ist

von A. S. (Gast)


Lesenswert?

Rolf M. schrieb:
>> Hat (1==0){} auch einen Effekt?
> Gute Frage. Das ist wohl Ansichtssache.

Und was ist der Unterschied zwischen
1
if(1==0)
und
1
{int foo = 0;}
??

von MaWin (Gast)


Lesenswert?

In C sind Statements ohne Auswirkungen bzw. herausoptimierte Statements 
die Regel.
z.B. bei der Verwendung von Abfragen in typunabhängigen Makros, wo immer 
nur eine Abfrage zur Compilezeit zutrifft.

Deshalb kann der Compiler das nicht warnen.

von Wilhelm M. (wimalopaan)


Lesenswert?

Torsten R. schrieb:
>
> Der wesentliche Unterschied wäre, dass man in C++ ausdrücken kann, dass
> &foo nicht 0 sein kann.

Und wenn es aus anderen Gründen unbedingt ein Zeiger sein muss, dann 
sollte man
auch z.B. not_null<> statt rohe Zeiger verwenden.  Damit ist der 
Aufrufer gezwungen, den Vertrag einzuhalten und der Aufgerufene braucht 
es nicht zu prüfen...

Und generell siehe CPL.1:

http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-cpl

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

MaWin schrieb:
> Deshalb kann der Compiler das nicht warnen.

Kann er sehr wohl, falls es sich um eine Variable handelt, die zwar
initialisiert, aber ansonsten nie benutzt wird.  Macht er auch.

Dürfte aber für das Problem des TEs nichts bringen, da er uns ja nicht
seinen kompletten Code, sondern nur ein Stück daraus gezeigt hat.

von MaWin (Gast)


Lesenswert?

Jörg W. schrieb:
> Kann er sehr wohl, falls es sich um eine Variable handelt, die zwar
> initialisiert, aber ansonsten nie benutzt wird.  Macht er auch.

Ja in diesem einen Spezialfall tut er es. In 1000 anderen Fällen tut er 
es nicht.

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.