Should one really set pointers to `NULL` after freeing them?

前端 未结 10 1063
庸人自扰
庸人自扰 2020-11-28 03:27

There seem to be two arguments why one should set a pointer to NULL after freeing them.

Avoid crashing when double-freeing pointers.

Short

10条回答
  •  迷失自我
    2020-11-28 03:51

    I don't do this. I don't particularly remember any bugs that would have been easier to deal with if I did. But it really depends on how you write your code. There are approximately three situations where I free anything:

    • When the pointer holding it is about to go out of scope, or is part of an object which is about to go out of scope or be freed.
    • When I am replacing the object with a new one (as with reallocation, for instance).
    • When I am releasing an object which is optionally present.

    In the third case, you set the pointer to NULL. That's not specifically because you're freeing it, it's because the whatever-it-is is optional, so of course NULL is a special value meaning "I haven't got one".

    In the first two cases, setting the pointer to NULL seems to me to be busy work with no particular purpose:

    int doSomework() {
        char *working_space = malloc(400*1000);
        // lots of work
        free(working_space);
        working_space = NULL; // wtf? In case someone has a reference to my stack?
        return result;
    }
    
    int doSomework2() {
        char * const working_space = malloc(400*1000);
        // lots of work
        free(working_space);
        working_space = NULL; // doesn't even compile, bad luck
        return result;
    }
    
    void freeTree(node_type *node) {
        for (int i = 0; i < node->numchildren; ++i) {
            freeTree(node->children[i]);
            node->children[i] = NULL; // stop wasting my time with this rubbish
        }
        free(node->children);
        node->children = NULL; // who even still has a pointer to node?
    
        // Should we do node->numchildren = 0 too, to keep
        // our non-existent struct in a consistent state?
        // After all, numchildren could be big enough
        // to make NULL[numchildren-1] dereferencable,
        // in which case we won't get our vital crash.
    
        // But if we do set numchildren = 0, then we won't
        // catch people iterating over our children after we're freed,
        // because they won't ever dereference children.
    
        // Apparently we're doomed. Maybe we should just not use
        // objects after they're freed? Seems extreme!
        free(node);
    }
    
    int replace(type **thing, size_t size) {
        type *newthing = copyAndExpand(*thing, size);
        if (newthing == NULL) return -1;
        free(*thing);
        *thing = NULL; // seriously? Always NULL after freeing?
        *thing = newthing;
        return 0;
    }
    

    It's true that NULL-ing the pointer can make it more obvious if you have a bug where you try to dereference it after freeing. Dereferencing probably does no immediate harm if you don't NULL the pointer, but is wrong in the long run.

    It's also true that NULL-ing the pointer obscures bugs where you double-free. The second free does no immediate harm if you do NULL the pointer, but is wrong in the long run (because it betrays the fact that your object lifecycles are broken). You can assert things are non-null when you free them, but that results in the following code to free a struct which holds an optional value:

    if (thing->cached != NULL) {
        assert(thing->cached != NULL);
        free(thing->cached);
        thing->cached = NULL;
    }
    free(thing);
    

    What that code tells you, is that you've got in too far. It should be:

    free(thing->cached);
    free(thing);
    

    I say, NULL the pointer if it's supposed to remain usable. If it isn't usable any more, best not to make it falsely appear to be, by putting in a potentially-meaningful value like NULL. If you want to provoke a page fault, use a platform-dependent value which isn't dereferancable, but which the rest of your code won't treat as a special "everything is fine and dandy" value:

    free(thing->cached);
    thing->cached = (void*)(0xFEFEFEFE);
    

    If you can't find any such constant on your system, you may be able to allocate a non-readable and/or non-writeable page, and use the address of that.

提交回复
热议问题