free a pointer and have it point to NULL

Discussion in 'C Programming' started by G G, Apr 22, 2014.

  1. Woo, really? I find that rather surprising. Does this mean that free()
    specifically is a special case or was this just a writing error on your
    part? If free behaved like all other functions, the value of ptr would
    be well defined after the free (to be the same value as before the free,
    since pointers are call-by-value).

    Defererencing that pointer after a free has obviously indeterminate
    consequences, but is also the pointer *value* indeterminate?

    Regards,
    Johannes

    --
    Ah, der neueste und bis heute genialste Streich unsere großen
    Kosmologen: Die Geheim-Vorhersage.
    - Karl Kaos über Rüdiger Thomas in dsa <hidbv3$om2$>
     
    Johannes Bauer, Apr 29, 2014
    #21
    1. Advertisements

  2. Yes: "The value of a pointer becomes indeterminate when the object it
    points to (or just past) reaches the end of its lifetime." (6.2.4 p2).

    I think you are reading too much into the phrase. "Becomes
    indeterminate" does not mean that any of the bits have to change. If
    the standard said "becomes invalid" I don't think you'd worry about it.

    A pointer bit pattern could, for example, become invalid as a result of
    the free function fiddling with some memory management registers; any
    reference to that value might then cause undefined behaviour (which,
    remember, includes nothing bad happening). The standard has a term for
    that sort of value: "indeterminate". It's unfortunate that "becomes
    indeterminate" suggest that you can't know anything about the bits that
    make up the value, but, in the C standard, the value of an object is an
    interpretation of its bits, and that can change without the bits
    themselves changing.
    The representation in the object does not need to change for it to
    represent a trap representation -- a term is just standardese for a
    value you can't use without undefined behaviour. When you unpack the
    definitions of various the terms involved ("indeterminate value",
    "unspecified value", "trap representation"), you end up with this
    uncontroversial observation: before the call to free, the pointer is a
    "valid value of the relevant type", but afterwards it "need not
    represent a value of the object type".
     
    Ben Bacarisse, Apr 29, 2014
    #22
    1. Advertisements

  3. G G

    Stefan Ram Guest

    Sometimes, in C there is a irritating distinction some people make
    between address values and pointers. Some people say that &x is an
    address value, but a pointer is /an object/ storing an address value.

    There is some support in N1570 itself for this:

    »A pointer type describes an object whose value provides
    a reference to an entity of the referenced type.«

    It says »an object«, not »a value«!

    I think the specification is misguided (written in an
    inconsistend and too vague way) in this regard, but when
    one takes this serious, we have this:

    char * a; if( a = malloc( 1 ))
    { char * b = a;
    free( a );
    /* Now your quotation has /not/ said that b has become
    indeterminate, because according to the pointer=object
    point of view, b is a pointer different from a. */ }
     
    Stefan Ram, Apr 29, 2014
    #23
  4. G G

    Stefan Ram Guest

    Sorry. Having re-read your quotation, I see that I erred in this regard.
     
    Stefan Ram, Apr 29, 2014
    #24
  5. G G

    Kaz Kylheku Guest

    A pointer which refers to any object object that has been destroyed
    (a "dangling pointer") is indeterminate. This is not limited to the free
    function: pointers to objects defined in automatic storage (block scope
    non-static local variables) also become indeterminate when their enclosing
    block terminates.

    This can happen without a change in the value which is stored. The value
    remains the same, but its meaning changes. The value is no longer on the
    roster of valid pointers, and so it is possible to diagnose any use of that
    value, even a harmless transmission from one variable to another without
    dereferencing.

    A tracing technique related to garbage collection could be used to find
    all dangling pointers and overwrite them with a value like null or some other
    value deemed useful for debugging. Because such pointers have indeterminate
    values, implementors have this freedom.

    By far the most common implementation is that nothing happens to such values at
    all, and when the storage is re-used for a new object, dangling pointers appear
    to be valid pointers referencing the new object.

    The rule allows for sophisticated debugging tools which catch problems as early
    as possible.

    Usually, any use whatsoever of a dangling pointer indicates a bug; there is no
    useful purpose behind it.

    The exception are introspective programs designed to inspect the behaviors of
    memory allocation: to answer questions like "is an object recycled
    immediately?":

    {
    char *p = malloc(10), *q;
    free(p);
    q = malloc(10);

    if (p == q) {
    puts("ten byte object was recycled immediately");
    }

    free(q);
    }

    Or:

    void *p, *q;

    {
    int x;
    p = &x;
    }

    {
    int y;
    q = &y;
    }

    if (p == q) {
    puts("Cool: x and y were placed in overlapping storage");
    }

    The rules in C are such that an implementation can diagnose these programs, and
    even halt their execution.

    (There is a workaround for that: to use memcmp to compare the pointers.
    However, that raises the risk of the comparison being spoiled by
    padding bits in a pointer which change its bitwise image, but not
    it value (where it points)).
    It is the indeterminacy of the value which causes the behavior, upon
    dereferencing, to be undefined.

    The idea that the pointer value remains "good" from the point of view
    of access, but cannot be dereferenced, would require a new category of value in
    C, which isn't worth it: a category that isn't "indeterminate", but something
    else.

    There is a pointer value which has that behavior: it can be used, but not
    dereferenced. Namely, the null pointer. However, a location which might hold a
    null pointer can be accessed for a useful purpose: to test whether or not it is
    null.
     
    Kaz Kylheku, Apr 29, 2014
    #25
  6. G G

    Kaz Kylheku Guest

    (But does not provide pass-by-reference, haha.)
    It says "object, whose value ..."!

    C types describe objects: i.e. they specify an in-memory representation
    that can be broken down into bits and bytes (the quantity of the latter being
    the "sizeof" that type).

    C values are manipulated in the data processor such that they are not always
    objects, but their type specifies details about what they look like when they
    are stored.

    The above sentence is primarily concerned with defining "pointer type".
     
    Kaz Kylheku, Apr 29, 2014
    #26
  7. G G

    James Kuyper Guest

    Yes.
    "... The value of a pointer becomes indeterminate when the object it
    points to (or just past) reaches the end of its lifetime." (6.2.4p2)
    "The lifetime of an allocated object extends from the allocation
    until the deallocation." (7.22.3p1)
    "The free function causes the space pointed to by ptr to be deallocated
    ...." (7.22.3.3p2)

    Keep in mind that it is possible for the value of a pointer to become
    indeterminate without any change to it's bit patterns. There are real
    systems which have the ability to make blocks of memory switch from
    being valid to being invalid while a program is running, and because of
    the clauses listed above, a fully conforming implementation of free()
    could have that effect.

    I personally believe that such changes are the only permitted way for
    free() to render a pointer invalid - that it is not permitted to modify
    the bit pattern of the pointer itself. However, I've had extended
    discussions about that on this newsgroup with people who felt otherwise,
    with neither side ever accepting the validity of the other's arguments.

    Keep in mind that all pointers anywhere in the program that point at any
    part of an allocated block of memory have an indeterminate value after
    that block is deallocated. Nothing remotely resembling the normal
    semantics for C function calls would allow all of those pointers to have
    their bit patterns modified just by calling free() on one of those pointers.
     
    James Kuyper, Apr 29, 2014
    #27
  8. A compiler could treat the free() function specially, but it's unlikely
    that

    char *p = malloc(10);
    if (p ! NULL) {
    p ++;
    free(p-1);
    }

    would modify the representation if p.

    Furthermore, the bytes that make up the representation of p are
    themselves objects which "retain their last-stored values between
    program startup and program termination".

    On the other hand, I seem to recall that there's a DR that says free()
    *can* change the representation of a pointer object after its value is
    passed to free(). I'll try to track it down later.
     
    Keith Thompson, Apr 29, 2014
    #28
  9. For protected mode x86 (80286 and up) loading a segment selector
    for a non-allocated segment will fail. That is, a segmentation
    violation. (You always wondered where the name came from?)

    Now, most compilers won't actually load one into a segment
    register when not needed to actually reference data, but the
    standard allows it to fail.

    (Selector zero is reserved as the null segment, so there is some
    value to use when there is nothing else to load.)

    -- glen
     
    glen herrmannsfeldt, Apr 29, 2014
    #29
  10. G G

    James Kuyper Guest

    On 04/29/2014 09:59 AM, James Kuyper wrote:
    ....
    I left out a key step: on such systems it is often the case that loading
    an invalid into an address register is sufficient to cause your program
    abort. Dereferencing the pointer isn't necessary. This is a safety
    measure: the designers felt that any program written badly enough to
    treat an invalid address as if were a valid one, is sufficiently
    dangerous that it's safest to have a policy of stopping such programs as
    soon as possible.
     
    James Kuyper, Apr 29, 2014
    #30
  11. It's C99 DR #260, submitted by Clive D.W. Feather in 2004.
    http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm

    As far as I can tell, the DR didn't lead to any specific wording in the
    C11 standard, but the committee response was:

    Question 1:
    Values may have any bit-pattern that validly represents them
    and the implementation is free to move between alternate
    representations (for example, it may normalize pointers,
    floating-point representations etc.). In the case of an
    indeterminate value all bit-patterns are valid representations
    and the actual bit-pattern may change without direct action of
    the program.

    Question 2:
    If two objects have identical bit-pattern representations and
    their types are the same they may still compare as unequal
    (for example if one object has an indeterminate value) and if
    one is an indeterminate value attempting to read such an object
    invokes undefined behavior. Implementations are permitted to
    track the origins of a bit-pattern and treat those representing
    an indeterminate value as distinct from those representing a
    determined value. They may also treat pointers based on different
    origins as distinct even though they are bitwise identical.

    In reference to "Question 1", I agree that it makes sense to permit an
    implementation to normalize pointers and floating-point objects when two
    or more representations have the same value. I wrote above that the
    bytes making up the representation of an object are themselves objects,
    which are required by 6.2.4p2 to "retain[] its last-stored value
    throughout its lifetime".

    I'm no longer quite as sure of that, given the way 6.2.6.1 defines
    *object representation*:

    Values stored in non-bit-field objects of any other object type
    consist of n × CHAR_BIT bits, where n is the size of an object
    of that type, in bytes. The value may be copied into an object
    of type unsigned char [n] (e.g., by memcpy); the resulting set
    of bytes is called the *object representation* of the value.

    which talks about the bytes *after copying them from the object*,
    so at least that paragraph doesn't imply that the bytes within the
    object itself are objects.

    On the other hand, an object is defined as a "region of data storage in
    the execution environment, the contents of which can represent values";
    a byte within a pointer object, for example, would seem to qualify.

    On the other other hand, a program that depends on a byte within a freed
    pointer retaining its previous value would be perverse, and I don't feel
    *too* bad about the possibility of breaking it.
     
    Keith Thompson, Apr 29, 2014
    #31
  12. G G

    James Kuyper Guest

    On 04/29/2014 02:50 PM, Keith Thompson wrote:
    ....
    At that level, I would agree. However, if 6.2.4p2 is not to be
    interpreted as prohibiting such changes, it renders essentially all uses
    of memcmp() useless, at least for any type that is allowed to have
    multiple representations of the same value (which could includes any
    integer type with padding bits, any signed type with anything other 2's
    complement representation, and all floating point and pointer types).
     
    James Kuyper, Apr 29, 2014
    #32
  13. More specifically, the way x86 processors starting with the 80286
    work, is that when you load a segment selector into a segment
    register, the processor also loads a segment descriptor from the
    appropriate entry in a descriptor table. If the descriptor is
    invalid, an interrupt occurs.
    C compilers I know tend to avoid loading segment registers until
    they are pretty much ready to use them. For example, pointer
    assignment and pointer comparison are not done using a segment
    register. (I don't believe that there are compare instructions
    for segment registers. Assignment could be done through load
    and store, though.)
    There have been many systems where you could load from any memory
    address, such as real mode MS-DOS. Some programmers for those
    systems tend to ignore fetch from bad addresses, such as past the
    end of arrays. (In most cases, just after the end of an array is
    still part of your address space.)

    Much code for languages that don't require short-circuit IF
    evaluation works because the out of bounds fetch doesn't cause
    any problems.

    And, by the way, all the segment selector logic is still in
    current, and likely future, x86 processors. It isn't some strange
    system that died out years ago, now only found in a museum.
    (But there are some of those, too.)

    -- glen
     
    glen herrmannsfeldt, Apr 29, 2014
    #33
  14. There is a memory allocations scheme that some of my collegues invented back in the 1980s that was then known as "Handles" which used double pointers.It allowed for memory to be moved behind the scenes (back when HARD DRIVESwould only hold 5MB). Since you could make copies of the Handle, it was possible to set the actual address to zero (or some other illegal value) whenthe memory ultimately represented by the Handle was freed. Of course, if you took a shortcut and actually wrote down a reference to the memory yourself, problems could ensue (but the memory could have been moved behind the scenes anyway, so most people didn't).

    However, I routinely set pointers to an illegal value when I want to know whether something has been allocated and then deallocated. I often have macros to allocate and deallocate.

    #define gimme(p,t,size) {if ((!p) && (!(p = (t) malloc (size)))) dealwithit();}
    #define lose(p) {if (p) free (p); p = 0;}
     
    Michael Angelo Ravera, May 3, 2014
    #34
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.