Problem mit dem NULL Pointer



  • Den NULL-Pointer nutzt man üblicherweise um anzuzeigen dass der Pointer auf nichts sinnvolles zeigt. Natürlich kann man den Zeiger später auf was sinnvolles zeigen lassen, einen Zeiger der immer auf nichts zeigt kann man sich auch sparen.

    Hier ein Beispiel, ein Stack. (OMFG das ist kein Stack sondern ..... eine Liste!!!!) (OMFG!!!!! MALLOC!!!! AAAAAAAAAAAHHHHHHHHHHHHHHHHH)

    struct stack{
        struct stackelement *first;
    };
    
    struct stackelement{
        struct stackelement *previous;
        void *value;
    };
    
    struct stack *createStack(){
        struct stack *s = malloc(sizeof(struct stack));
        s->first = 0; //hier zeigt first auf NULL weil es kein erstes Element gibt
        return s;
    }
    
    void push(struct stack *s, void *v){ //legt ein Element auf den Stack
        struct stackelement *se = malloc(sizeof(struct stack));
        se->value = v;
        if (!s->first){ //lies: wenn es kein erstes Element gibt
            s->first = se; //dann ist unser neues Element das erste Element
            s->previous = 0; //weitere Elemente haben wir aber nicht
        }
        else{ //es gibt ein erstes Element
            se->previous = s->first; //dieses erste Element ist jetzt das zweite Element
            s->first = se; //das neue Element ist jetzt das erste
        }
    }
    
    void *pop(struct stack *s){ //holt ein Element vom Stack
        if (!s->first) return 0; //es exestiert kein Element das man holen könnte
        void *v = s->first->value; //der Wert des ersten Elements muss zurückgegeben werden
        struct stackelement *temp = s->first; //wir merken uns das Element was es nun nicht mehr geben soll
        s->first = s->first->previous; //das nächste Element ist nun das erste
        free(temp); //das nicht mehr benötigte Element wird gelöscht.
        return v; //schließlich wird der Wert zurückgegeben
    }
    

    Wie du an den Kommentaren erkennst wird 0 immer für "Objekt auf das der Zeiger zeigt existiert nicht" benutzt und mit if (zeiger) wird geprüft ob der Zeiger auf ein Objekt zeigt.

    Nach Ansicht des Forums ist der obige Code böse, weil malloc drin ist und es verhindert werden kann. Wenn du es forengerecht haben willst musst du einen void *buffer[12345] nehmen und dort die Elemente reinschreiben und dir den Index merken. Außerdem steht öfter mal in dem if- und else-Zweig dasselbe womit man das entsprechend vor oder nach der Verzweigung hinschreiben kann und es fehlen noch einige Funktionen bevor man den Stack sinnvoll einsetzen kann, aber der Sinn von NULL-Pointern sollte klar geworden sein.

    BTW ich habe immer 0 statt NULL geschrieben. Ist eigentlich egal. In der stdlib.h steht #define NULL (void *)0 drin.



  • also NULL ist eigentlich eine definition ist so oder so ähnlich definiert

    #define NULL ((void*)0)
    

    sagen wir mal du baust eine verkettete liste

    typedef struct _list{
        int data;
        struct _list *next;
    }list
    
    list *newListNode(int data){
        list *ret = malloc(sizeof(list));
        ret->data = data;
        ret->next = NULL;
        return data;
    }
    
    int main(){
        list *a = newListNode(1);
        list *b = newListNode(2);
        a->next = b;
        //wenn du jetzt die liste durchläufst mußt natürlich wissen wo schluß ist also
        while(a->next!=NULL){
            printf("%d",a->data);
            a = a->next;
        }
    }
    

    hoffe da sind nicht all zu viele fehler drin;)



  • oh da war ich bischen zu langsam 😞



  • oh und es waren doch ne ganze menge 😮

    typedef struct _list{
        int data;
        struct _list *next;
    }list;
    
    list *newListNode(int data){
        list *ret = malloc(sizeof(list));
        ret->data = data;
        ret->next = NULL;
        return ret;
    }
    
    int main(){
        list *a = newListNode(1);
        list *b = newListNode(2);
        a->next = b;
        //wenn du jetzt die liste durchläufst mußt natürlich wissen wo schluß ist also
        while(a!=NULL){
            printf("%d",a->data);
            a = a->next;
        }
    }
    


  • ES schrieb:

    Ich dachte der wäre genau für so eine Situation, wenn man einen Zeiger initialisieren möchte, aber eig. noch nichts hat wo er drauf zeigen soll.

    Ja, das gehört zum guten Stil. Der Zeiger ist sozusagen entschärft. Um ihn benutzen zu können, musst du ihm vorher eine gültige Adresse zuweisen.

    ES schrieb:

    Oder für der NULL Point nur dazu, dass der Pointer auf kein gültiges Datenobjekt zeigt und deshalb kann auch später kein Wert übergeben werden?

    Klar kannst du später einen Wert übergeben, wenn es kein const Zeiger ist.

    ES schrieb:

    Doch wofür verwende ich NULL dann?

    NULL ist in einigen Fällen praktisch:
    Bei verketteten Listen, um den Anfang oder das Ende der Liste zu kennzeichnen und um Elemente anhängen zu können.
    Als Rückgabewert einer Funktion, um zu signalisieren das etwas schiefgelaufen ist.
    Bei der Reservierung und Freigabe von Speicher ( realloc, free ).
    Wenn du Zeigerarrays hast kann es sein, das nicht bekannt ist, welche Elemente des Array einen gültigen Zeiger erhalten haben. Initialisierst du alle Arrayelemente mit Null, brauchst du dich nicht um jedes einzelne Element bei der Freigabe zu kümmern, weil ein free(NULL) nicht schadet.
    Etc.

    nwp2 schrieb:

    Nach Ansicht des Forums ist der obige Code böse, weil malloc drin ist und es verhindert werden kann. Wenn du es forengerecht haben willst musst du einen void *buffer[12345] nehmen und dort die Elemente reinschreiben und dir den Index merken.

    Das ist doch Käse und das weißt du auch.



  • ums klar und deutlich zu sagen NULL != 0 !!! betrifft vor allem den code von nwp2 der ja immer schön 0 verwendet hat... denn der null zeiger zeigt nicht notwendiger weise auf 0



  • noobLolo schrieb:

    ... denn der null zeiger zeigt nicht notwendiger weise auf 0

    Sondern? 0L in C++?



  • wie schon beschrieben ist es eine definition die genau so gut so aussehen könnte

    #define NULL 0x1
    

    es muß dann nur sichergestellt werden dass malloc keine adresse 0x1 zurück gibt.



  • nwp2 schrieb:

    noobLolo schrieb:

    ... denn der null zeiger zeigt nicht notwendiger weise auf 0

    Sondern? 0L in C++?

    In C++ ist das einfacher. Aber hier ist nur gefragt, wie es in C ist.



  • noopLolo schrieb:

    wie schon beschrieben ist es eine definition die genau so gut so aussehen könnte

    #define NULL 0x1
    

    es muß dann nur sichergestellt werden dass malloc keine adresse 0x1 zurück gibt.

    Sowas kann echt passieren? Es wäre reichlich fatal wenn if(NULL) wahr wäre.

    In der Doku auf http://www.cplusplus.com/reference/clibrary/cstddef/NULL/ steht
    ^This macro expands to a null pointer constant.

    A null pointer is generally used to signify that a pointer does not point to any object.
    In C++, NULL expands either to 0 or 0L.^

    Scheinbar kann man tatsächlich NULL als 12345 definieren. Weshalb man sowas machen sollte ist mir aber unklar.

    Heißt, entweder die Pointer auf NULL setzen und mit if(pointer==NULL) testen oder gleich 0 benutzen.



  • nwp2 schrieb:

    Heißt, entweder die Pointer auf NULL setzen und mit if(pointer==NULL) testen

    ja so sollte man das machen wenn mans sauber haben will.

    genauso wie

    void *data = malloc(sizeof(char));
    if(data==NULL)
       THROW_ERROR("hab kein speicher");
    

    statt

    void *data = malloc(sizeof(char));
    if(data)
       printf("hab speicher");
    


  • nwp2 schrieb:

    Heißt, entweder die Pointer auf NULL setzen und mit if(pointer==NULL) testen oder gleich 0 benutzen.

    gleich 0 benutzen. wenn ich mich recht erinnere, wird bei 'nem void p = 0; die 0 in die pointer-ungültigmach-nullpointer-konstante konvertiert (also auch wenn sie (void)123 ist). ich benutze das makro 'NULL' nie, gab noch niemals zickereien deshalb. den sinn einer nullpointerkonstanten != 0 kenne ich aber auch nicht.

    noobLolo schrieb:

    void *data = malloc(sizeof(char));
    if(data==NULL)
       THROW_ERROR("hab kein speicher");
    

    was iss'n 'THROW_ERROR'? ein verstecktes longjmp? *fg*
    übrigens kannste dir das 'sizeof(char)' sparen.
    🙂



  • nwp2 schrieb:

    Sowas kann echt passieren?

    hab mal folgendes gefunden unter http://www.lysator.liu.se/c/c-faq/c-1.html

    The Prime 50 series used segment 07777, offset 0 for the null pointer, at least for PL/I. Later models used segment 0, offset 0 for null pointers in C, necessitating new instructions such as TCNP (Test C Null Pointer), evidently as a sop to all the extant poorly-written C code which made incorrect assumptions. Older, word-addressed Prime machines were also notorious for requiring larger byte pointers (char *'s) than word pointers (int *'s).

    The Eclipse MV series from Data General has three architecturally supported pointer formats (word, byte, and bit pointers), two of which are used by C compilers: byte pointers for char * and void *, and word pointers for everything else.

    Some Honeywell-Bull mainframes use the bit pattern 06000 for (internal) null pointers.

    The CDC Cyber 180 Series has 48-bit pointers consisting of a ring, segment, and offset. Most users (in ring 11) have null pointers of 0xB00000000000.

    The Symbolics Lisp Machine, a tagged architecture, does not even have conventional numeric pointers; it uses the pair <NIL, 0> (basically a nonexistent <object, offset> handle) as a C null pointer.

    Depending on the "memory model" in use, 80*86 processors (PC's) may use 16 bit data pointers and 32 bit function pointers, or vice versa.

    The old HP 3000 series computers use a different addressing scheme for byte addresses than for word addresses; void and char pointers therefore have a different representation than an int (structure, etc.) pointer to the same address would have.

    nwp2 schrieb:

    Es wäre reichlich fatal wenn if(NULL) wahr wäre.

    When C requires the boolean value of an expression (in the if, while, for, and do statements, and with the &&, ||, !, and ?: operators), a false value is produced when the expression compares equal to zero, and a true value otherwise. That is, whenever one writes

    if(expr)

    where "expr" is any expression at all, the compiler essentially acts as if it had been written as

    if(expr != 0)

    Substituting the trivial pointer expression "p" for "expr," we have

    if(p) is equivalent to if(p != 0)

    and this is a comparison context, so the compiler can tell that the (implicit) 0 is a null pointer, and use the correct value. There is no trickery involved here; compilers do work this way, and generate identical code for both statements. The internal representation of a pointer does not matter.

    The boolean negation operator, !, can be described as follows:

    !expr is essentially equivalent to expr?0:1

    It is left as an exercise for the reader to show that

    if(!p) is equivalent to if(p == 0)

    "Abbreviations" such as if(p), though perfectly legal, are considered by some to be bad style.


Anmelden zum Antworten