Problem NULL Pointer in case Konstruktionen
-
fricky schrieb:
eine 0 z.b.
der satz sagt aus, dass 0 und )void*)0 das selbe sind, also eine nullpointer-konstante.if (irgendein_pointer == 0) { // passt immer, wenn man testen will, ob's ein nullpointer ist }
Richtig, nur war das ja nicht das Thema. camper hat es schon geschrieben, es ging darum
(ptrdiff_t) 0 == (ptrdiff_t)(void*) 0
was nicht zwangsläufig wahr sein muss. Allerdings ist dies auf vielen Systemen trotzdem wahr, da der Nullzeiger eben dem Wert 0 entspricht, also alle Bits 0. Es geht nur darum, dass es für das eigentliche Problem bessere Lösungen gibt und das Brechen der Plattformunabhängigkeit an dieser Stelle absolut unnötig ist.
-
camper schrieb:
Zu deiner Anmerkung? Wieso in C nur mit void* reversibel? Wieso gilt das nicht zwingend bspweise mit int* ?
Weil der C-Standard diesbezüglich keine Aussage zu anderen Zeigertypen macht.
alles klar.
-
groovemaster schrieb:
es ging darum
(ptrdiff_t) 0 == (ptrdiff_t)(void*) 0
was nicht zwangsläufig wahr sein muss.
wenn, wie wir nun alle wissen, 0 == (void)0* wahr ist, dann kann der selbe cast auf beiden seiten, ob nun nach ptrdiff_t, char oder double, auch nichts dran ändrn. 0 bleibt 0, das ist nun mal so.
-
fricky schrieb:
groovemaster schrieb:
es ging darum
(ptrdiff_t) 0 == (ptrdiff_t)(void*) 0
was nicht zwangsläufig wahr sein muss.
wenn, wie wir nun alle wissen, 0 == (void)0* wahr ist, dann kann der selbe cast auf beiden seiten, ob nun nach ptrdiff_t, char oder double, auch nichts dran ändrn. 0 bleibt 0, das ist nun mal so.
Ah... das bedeutet erst einmal nur, dass du nicht gut genug hingeschaut hast.
in0 == (void*)0;
wird der linke Operand des Gleichheitsoperators implizit in den Typ void* konvertiert, was, wie bereits erwähnt wurde, in einem Nullpointer des Typs void* resultiert. Diese implizite Konvertierung ist notwendig, weil == nur Werte des gleichen Typs vergleichen kann. Wir vergleichen also das Ergebnis der gleichen Operation auf beiden Seiten, jede 0 wird in einen Nullpointer des Typs void* konvertiert und das Ergebnis verglichen, was natürlich das Ergebnis true liefert. Die beiden Seiten unterscheiden sich nur im Grund für diese Konvertierung. Auf der rechten Seite ist diese durch den Cast-Operator bedingt, links ist es der Gleichheitsoperator, der zunächst beide Seiten (nach bestimmten Regeln) in einen gemeinsamen Typen konvertieren muss. In
NULL == (void*)0
hängt es nun davon ab, wie NULL auf einer konkreten Plattform implementiert ist. Ist das bereits ein Ausdruck vom Typ void* entfällt natürlich die implizite Konvertierung auf der linken Seite (weil sie durch das Makro bereits explizit erfolgt ist), sonst gilt das oben gesagte.
(ptrdiff_t) 0 == (ptrdiff_t)(void*) 0
ist etwas völlig Anderes. Auf der linken Seite wird die integrale Null in den Typ ptrdiff_t konvertiert. ptrdiff_t ist ein integraler Typ und Konvertierungen zwischen integralen Typen sind werterhaltend. Folglich hat die linke Seite den Wert 0. Auf der rechten Seite haben wir zunächst eine explizite Konvertierung nach void*, was in einem Nullpointer dieses Typs resultiert. Dieser Nullpointer wird dann explizit in den integralen Typ ptrdiff_t konvertiert, was nicht zwingend in dem Wert 0 resultiert. Der Vergleichsoperator wiederum bewirkt hier keine zusätzlichen impliziten Konvertierungen, denn beide Operanden haben bereits den gleichen Typ.
-
Also, ich denke, ich hab's mittlerweile verstanden, was camper und groovemaster erklärt haben.
Ich hab mir dann folgendes Beispiel geschrieben, wo ich NULL neu definiere und auf einen Wert ungleich 0 setze.
#include <stddef.h> #include <stdio.h> #ifdef NULL #undef NULL #define NULL (void*) 10 #define NULL_REDEF #endif #define ifprint(cond) \ printf("%0.2d: " #cond " ... ", ++count); \ fflush(stdout); \ if((cond)) \ printf("yes\n"); \ else \ printf("no\n") int main(void) { int count = 0; #ifdef NULL_REDEF printf("NULL was redefined\n"); #else printf("this test may not work\n"); #endif ifprint(0 == (void*) 0); ifprint(NULL == (void*)0); ifprint(NULL == 0); do { int *ptr = NULL; printf("ptr = %p\n", ptr); ifprint(ptr); ifprint(ptr == NULL); ifprint(ptr == 0); ifprint(ptr == (void*) 0); ifprint(ptr == (void*) 10); } while(0); return 0; }
Die Ausgabe
$ gcc -dumpversion 4.1.2 $ gcc null.c -onull $ ./null NULL was redefined 01: 0 == (void*) 0 ... yes 02: NULL == (void*)0 ... no 03: NULL == 0 ... no ptr = 0xa 04: ptr ... yes 05: ptr == NULL ... yes 06: ptr == 0 ... no 07: ptr == (void*) 0 ... no 08: ptr == (void*) 10 ... yes
Ich finde das Ergebnis überraschend, z.b. dass Test 06 fehlschlägt. Kann es sein, dass meine Redifinition von NULL völlig falsch ist und ich in der Tat den Nullpointer gar nicht neu definiert habe sondern nur den Macro?
-
fricky schrieb:
wenn, wie wir nun alle wissen, 0 == (void)0* wahr ist, dann kann der selbe cast auf beiden seiten, ob nun nach ptrdiff_t, char oder double, auch nichts dran ändrn.
Doch. Der Cast verhindert eben das gleiche Verhalten wie bei der impliziten Umwandlung. Ich bin mal so frei und verweise auf campers letzten Absatz.
supertux schrieb:
Ich finde das Ergebnis überraschend, z.b. dass Test 06 fehlschlägt.
Das ist allerdings absolut plausibel. "0" wird ja immer noch in den im Compiler implementierten "echten" Nullzeiger umgewandelt, unabhängig von NULL. Eine Redefinition von NULL bringt hier nichts. Du müsstest dem Compiler schon beibringen, dass der Nullzeiger die Adresse 0xa hat.
supertux schrieb:
Kann es sein, [...] ich in der Tat den Nullpointer gar nicht neu definiert habe sondern nur den Macro?
Genau so ist es.
-
groovemaster schrieb:
fricky schrieb:
wenn, wie wir nun alle wissen, 0 == (void)0* wahr ist, dann kann der selbe cast auf beiden seiten, ob nun nach ptrdiff_t, char oder double, auch nichts dran ändrn.
Doch. Der Cast verhindert eben das gleiche Verhalten wie bei der impliziten Umwandlung.
ach, stimmt ja! ok, ich hab' mich total getäuscht.
auch danke an camper für die ausführliche erklärung.
-
camper schrieb:
Dieser Nullpointer wird dann explizit in den integralen Typ ptrdiff_t konvertiert, was nicht zwingend in dem Wert 0 resultiert.
Aber da ganz rechts steht doch eine 0.
Was soll denn sonst für ein Wert da reinkonvertiert werden?
-
conversion n00b schrieb:
camper schrieb:
Dieser Nullpointer wird dann explizit in den integralen Typ ptrdiff_t konvertiert, was nicht zwingend in dem Wert 0 resultiert.
Aber da ganz rechts steht doch eine 0.
Was soll denn sonst für ein Wert da reinkonvertiert werden?
Die Konvertierung einer Nullpointerkonstanten in einen Zeiger unterscheidet sich von der expliziten Konvertierung eines - nicht konstanten - Ausdrucks, der nur zufällig den Wert 0 hat. Falls du mit C++ vertraut bist: dies entspricht der Verwendung von static_cast (bzw. implizite Konvertierung) vs. reinterpret_cast.
Motivierend kann man sich ein hypothetisches System vorstellen, das nicht furchtbar exotisch ist, bei dem diese Unterscheidung nützlich ist:
auf einem PC haben heute Daten- und Adressbus die gleiche Breite - das ist nicht immer so. Schauen wir aber mal zum Beispiel zum alten 8086: hier haben wir 20bit für Adressen und 16bit für Daten. Dargestellt werden Adressen dort durch zwei 16bit-Wörter - zusammen 32bit, wobei - wegen des besonderen Adressierungsschemas - jede Adresse 4096 verschiedene Darstellungen hat. Letzteres ist eine Besonderheit und Folge der Segementierung, man kann sich ohne weiteres ein System vorstellen, das linear adressiert, z.B. 16bit für Daten aber nur 12 bit für Adressen. In diesem Falle entsprechen nur 2^12 Zahlen einer Adresse, die Konvertierung einer anderen Zahl ist dann undefiniert (könnte z.B. zu einer Traprepräsentation führen). Ein Nullpointer ist ein Zeiger, der garantiert auf kein Objekt zeigt. Nun möchte man aber nach Möglichkeit keine realen Adressen opfern (wie es beim PC der Fall ist) für diesen Zeiger, denn diese Adresse wäre dann nicht mehr legal durch ein C-Programm erreichbar. Man könnte also bei unserem System eine dieser Zahlen, die keiner realen Adresse entspricht, für die Nullpointerrepräsentation verwenden. Das bedeutet aber, dass wenn eine Nullpointerkonstante konvertiert wird, ggf. die Repräsentation verändert werden muss.
Beispiel: unser System (gemeint ist hier: der Compiler) soll die Darstellung 0xffff für den Nullpointer benutzen.
Dann resultiert:void* p = 0; // eine Nullpointerkonstante wird konvertiert und zugewiesen : Zeiger p wird jetzt durch 0xffff repräsentiert, das ist keine reale Adresse int i = (int)p; // sollte in i == 0xffff resultieren (weil diese Konvertierung das Adressierungsschema unserer Maschine abbilden soll) i = 0; p = (void*)i; // hier wird keine Nullpointerkonstante konvertiert, sondern die Adresse 0 in einen Zeiger umgewandelt ==> p wird durch 0x0000 repräsentiert i = (int)p; // i ist immer noch 0
-
groovemaster schrieb:
fricky schrieb:
wenn, wie wir nun alle wissen, 0 == (void)0* wahr ist, dann kann der selbe cast auf beiden seiten, ob nun nach ptrdiff_t, char oder double, auch nichts dran ändrn.
Doch. Der Cast verhindert eben das gleiche Verhalten wie bei der impliziten Umwandlung. Ich bin mal so frei und verweise auf campers letzten Absatz.
supertux schrieb:
Ich finde das Ergebnis überraschend, z.b. dass Test 06 fehlschlägt.
Das ist allerdings absolut plausibel. "0" wird ja immer noch in den im Compiler implementierten "echten" Nullzeiger umgewandelt, unabhängig von NULL. Eine Redefinition von NULL bringt hier nichts. Du müsstest dem Compiler schon beibringen, dass der Nullzeiger die Adresse 0xa hat.
supertux schrieb:
Kann es sein, [...] ich in der Tat den Nullpointer gar nicht neu definiert habe sondern nur den Macro?
Genau so ist es.
hab ich mir schon gedacht, als die Ergebnisse nicht dem entsprachen, was ich mir vorgestellt habe. Schade, würde gerne sowas im Realfall beobachten, aber ich werde 100% den GCC Code nur deswegen nicht verändern (krieg ja Kopfschmerzen, wenn ich mir diesen Code anschauen
)
Jedenfalls, danke an camper und groovemaster, sehr gute Erklärung, endlich hab ich den Unterschied verstanden.