Problem NULL Pointer in case Konstruktionen



  • Weil das eigentliche Programm riesige switch-Blöcke enthält.



  • Werner12 schrieb:

    Weil das eigentliche Programm riesige switch-Blöcke enthält.

    und? ehrlich gesagt, ich verstehe deine Erklärung nicht.



  • den Block kannst du doch so behalten 😉
    du musst doch nur die Zeile mit der Bedingung ändern

    Bsp.:

    int i;
    /* ... */
    switch(i) {
    case 1:
       /* 100 lines of code */
       break;
    case 2:
       /* 250 lines of code */
       break
    default:
       /* 150 lines of code */
       break;
    }
    

    das wären 508 Zeilen code, aber tatsächlich musst du nur 8 Zeilen verändern:

    if(i == 1) {
       /* 100 lines of code */
    } else if(i == 2) {
       /* 250 lines of code */
    } else {
       /* 150 lines of code */
    }
    

    die änderung sollte wohl nicht soo lange dauern 😉



  • Oder besser:
    die riesigen Switch-Blöcke in eigene Funktionen auslagern. (Rest wie bereits erklärt)



  • was haben deine switchblöcke, welche den wert einer integer auswerten mit dem intergerzeiger zu tun...?

    int *i=??????;
    
    printf("Pointer is %s\n", (i==NULL) ? "empty" : "valid");
    
    switch(*i){
    
      case 1:
      //....
      break;
      case 2:
      //....
      break;
      case 3:
      //....
      break;
      default:
      //....
      break;
    }
    


  • #include <stdio.h>
    
    int main(int argc, char **argv) {
      int*test=NULL;
      switch((int)test) {
        case (int)NULL:printf("is empty\n");break;
        default:printf("is not empty\n");
      };
    };
    


  • fricky schrieb:

    switch((ptrdiff_t)test) 
    {
       case 0:
       printf("is empty\n");
       break;
    ...
    ...
    ...
    

    🙂

    Das ist keine gute Idee. Denn das funktioniert auch nur auf Systemen, wo der Nullzeiger tatsächlich dem Wert 0 entspricht.



  • groovemaster schrieb:

    Denn das funktioniert auch nur auf Systemen, wo der Nullzeiger tatsächlich dem Wert 0 entspricht.

    ISO/IEC 9899:1999 schrieb:

    An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

    🙂



  • fricky schrieb:

    An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

    Du hast nicht verstanden, was der Satz aussagt. Schau dir am besten mal an, was eine integer constant expression ist. Im Beispiel wird maximal eine integer constant expression mit dem Wert 0 in ptrdiff_t umgewandelt, aber nicht in einen Zeiger.



  • groovemaster schrieb:

    Du hast nicht verstanden, was der Satz aussagt. Schau dir am besten mal an, was eine integer constant expression ist.

    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
    }
    

    🙂



  • Also wirklich *-freak/frick ich mach das immer folgendermaßen:

    > if(!ptr)

    alles andere ist blasphemie


  • Mod

    void* p = 0;      // Nullpointer (initialisiert mit einem konstanten Ausdruck)
    intptr_t i = (intptr_t)p;
    assert( i == 0 ); // nicht zwingend erfüllt
    i = 0;
    p = (void*)i;     // könnte in trap resultieren
    assert( p == 0 ); // nicht zwingend erfüllt
    

    Glücklicherweise ist dies die einzige Stelle, bei der die Semantik davon abhängt, ob der initialisierende Ausdruck ein konstanter Ausdruck ist oder nicht.

    kleine Anmerkung: die Konvertierung void* -> intptr_t -> void* ist in C nur mit void* reversibel (liefert den ursprünglichen Zeigerwert) - in C++ gilt dies für alle Zeigertypen



  • Bei deinem letzten assert sollte es nicht heißen

    assert( p == NULL ); // nicht zwingend erfüllt
    

    ???

    Zu deiner Anmerkung? Wieso in C nur mit void* reversibel? Wieso gilt das nicht zwingend bspweise mit int* ?


  • Mod

    supertux schrieb:

    Bei deinem letzten assert sollte es nicht heißen

    assert( p == NULL ); // nicht zwingend erfüllt
    

    Beides ist möglich, da auch ein einfaches 0 eine Nullpointerkonstante ist.

    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.



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


  • Mod

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

    0 == (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.


Anmelden zum Antworten