Re: Null pointers

From: Keith Thompson (kst-u_at_mib.org)
Date: 08/07/04

  • Next message: Ben Pfaff: "Re: What is wrong with this: *p++=*s++"
    Date: Sat, 07 Aug 2004 01:27:38 GMT
    
    

    Christian Bau <christian.bau@cbau.freeserve.co.uk> writes:
    > In article <ln3c30twl2.fsf@nuthaus.mib.org>,
    > Keith Thompson <kst-u@mib.org> wrote:
    > > Christian Bau <christian.bau@cbau.freeserve.co.uk> writes:
    > > [...]
    > > > Keep in mind that cast from int to char* is "implementation defined" and
    > > > doesn't necessarily give a useful result, so if you have a variable "int
    > > > x" then "(char *) x == NULL" could be true for many different values of
    > > > x.
    > > >
    > > > When you use a cast to convert say from int to float, the bits don't
    > > > stay unchanged: int i = 1; float f = (float) i; will produce very
    > > > different representations in i and f even though both are equal to one.
    > > > Same with a cast to convert from int to char*. Now typically compilers
    > > > where a null pointer has an all-bits-zero representation will leave the
    > > > representation unchanged, but if a null pointer has the same
    > > > representation as 0x12345678 then that would be no good.
    > >
    > > I'm going to use the notation $12345678$ to refer to a pointer whose
    > > internal representation is the same as the integer 0x12345678.
    > >
    > > Even an implementation with the null pointer represented as
    > > $12345678$, integer to pointer conversions could still leave the bits
    > > unchanged, except in the case of converting a null pointer constant to
    > > a pointer type. Remember that a null pointer constant is a source
    > > construct, and the conversion of a null pointer constant to a pointer
    > > value takes place during compilation.
    >
    > No, it does not, at least not in C99.

    You're right that the language doesn't require the conversion to be
    done during compilation; I was sloppy there. (But of course it can
    be, and it typically is.)

    > For example, in the definition of "equality operator" it says
    >
    > Constraints: (three other possibilities and)
    > One operand is a pointer and the other is a null pointer constant.
    >
    > Semantics: (two other possibilities and)
    > Otherwise, at least one operand is a pointer. If one operand is a
    > null pointer constant, it is converted to the type of the other operand.
    >
    > Conversion is done according to exactly the same rules as all other
    > conversions; since a null pointer constant is either of an integral type
    > or of type void*, that conversion is either a conversion from an
    > integral type to a pointer type or from void* to another pointer type.
    > But there is no difference between this conversion and any other
    > conversion. It is conceptually not a compile time construct. (Of course
    > an optimiser will determine the result of the conversion at compile
    > time, just as 2+3 will be determined at compile time by practically
    > every compiler).

    Conversion of a null pointer constant to a pointer type is explicitly
    a special case, at least in C99. (I think that was the intent in C90
    as well, but C99 expresses it better; this is speculation on my part.)

    > > There's no requirement to
    > > duplicate that conversion at run time.
    > >
    > > So we could have:
    > >
    > > (char*)0 --> $12345678$ (null pointer)
    > > int zero = 0;
    > > (char*)zero --> $00000000$ (non-null pointer)
    > > (char*)0x12345678 --> $12345678$ (happens to be a null pointer)
    >
    > Quite possible in C90, but most definitely not in C99. In C90, the
    > wording was such that in an assignment, or within an equality operator,
    > and probably some cases that I forgot, a null pointer constant was
    > replaced with a null pointer. (char*)0 was _not_ one if these cases and
    > in C90 not guaranteed to be a null pointer; in C99 they added that
    > _every_ conversion of a null pointer constant to a pointer produces a
    > null pointer.

    Here's the C90 wording, with underscores denoting italics:

        An integral constant expression with the value 0, or such an
        expression cast to type void *, is called a _null pointer
        constant_. If a null pointer constant is assigned to or compared
        for equality to a pointer, the constant is converted to a pointer
        of that type. Such a pointer, called a _null pointer_, is
        guaranteed to compare unequal to a pointer to any object or
        function.

        Two null pointers. converted through possibly different sequences
        of casts to pointer types, shall compare equal.

    C99 says:

        An integer constant expression with the value 0, or such an
        expression cast to type void *, is called a null pointer constant.
        If a null pointer constant is converted to a pointer type, the
        resulting pointer, called a null pointer, is guaranteed to compare
        unequal to a pointer to any object or function.

        Conversion of a null pointer to another pointer type yields a null
        pointer of that type. Any two null pointers shall compare equal.

    In my opinion, C99's statement that a null pointer constant yields a
    null pointer when converted to a pointer type does not imply that all
    expressions of type int with value 0 yield a null pointer when
    converted to a pointer type.

    In my example above, I presume the part you disagree with is my
    assertion that, given "int zero = 0;", the expression "(char*)zero"
    needn't yield a null pointer. Since "zero" is not a null pointer
    constant

    Your assumption, I think, is that conversion of a given value from a
    given type to another given type (in this case, respectively, the
    value 0, the type int, and the type char*) must always yield the same
    result. That's not a reasonable expectation, but I think it's
    overridden by the fact that conversion of a "null pointer constant" is
    explicitly a special case.

    More concretely:

    #include <stdio.h>
    #include <string.h>

    static int equal(char *x, char *y)
    {
        return memcmp(&x, &y, sizeof(char*)) == 0;
    }

    int main(void)
    {
        char *null_pointer = 0;
        char *all_bits_zero_pointer;
        int zero = 0;
        char *maybe_null_pointer = (char*)zero;

        memset(&all_bits_zero_pointer, 0, sizeof all_bits_zero_pointer);

        if (equal(null_pointer, all_bits_zero_pointer)) {
            printf("A null pointer is all-bits-zero\n");
        }
        else {
            printf("A null pointer is not all-bits-zero\n");
        }

        if (equal(null_pointer, maybe_null_pointer)) {
            printf("Conversion of int zero yields a null pointer\n");
        }
        else {
            printf("Conversion of int zero does not yield a null pointer\n");
        }

        return 0;
    }

    I'm using memcmp rather than direct pointer comparison to avoid any
    possibility of undefined behavior on attempts to access the value of
    an invalid pointer.

    I think we all agree that the first line of output from this program
    may be either
        A null pointer is all-bits-zero
    or
        A null pointer is not all-bits-zero

    I assert that, regardless of the first line of output, the second line
    can be either
        Conversion of int zero yields a null pointer
    or
        Conversion of int zero does not yield a null pointer
    under a conforming implementation -- though an implementation would
    have to be particularly perverse to produce
        A null pointer is not all-bits-zero
        Conversion of int zero does not yield a null pointer

    (The output happens to be
        A null pointer is all-bits-zero
        Conversion of int zero yields a null pointer
    on all systems I'm familiar with; that's not very illuminating.)

    To put it another way, I believe that choosing a value other than
    all-bits-zero for the null pointer does not imply that
    integer-to-pointer conversion has to do anything other than a bitwise
    copy *except* in the case of a null pointer constant.

    -- 
    Keith Thompson (The_Other_Keith) kst-u@mib.org  <http://www.ghoti.net/~kst>
    San Diego Supercomputer Center             <*>  <http://users.sdsc.edu/~kst>
    We must do something.  This is something.  Therefore, we must do this.
    

  • Next message: Ben Pfaff: "Re: What is wrong with this: *p++=*s++"

    Relevant Pages

    • Re: 0, NULL and variadic functions
      ... and a null pointer constant at the same time (with or without ... null pointer constant, without having pointer type. ... The integer constant 0 is specifically of type int; ... the conversion is performed *by the assignment*. ...
      (comp.lang.c)
    • Re: NULL with representation other then all bits 0
      ... >> I believe this has been added as a requirement in C99. ... expression cast to type void *, is called a null pointer constant. ... It says what the result of a conversion from an integer type ...
      (comp.lang.c)
    • Re: NULL with representation other then all bits 0
      ... > expression cast to type void *, is called a null pointer constant. ... An integer may be converted to any pointer type. ... It says what the result of a conversion from an integer type ...
      (comp.lang.c)
    • Re: ptr conversions and values
      ... >> each pointer pointed to a byte, ... Without that conversion, a pointer that points to an N-byte ... pointers to incomplete array types must indicate the start of the array ... >> It's all right, mostly equivalent with the previous one, representation ...
      (comp.std.c)
    • Re: does a program work in all cases?
      ... A null pointer constant has whatever type it has as an expression. ... long int, etc.), or "such an expression cast to type void*", which has ... the result of the conversion is of type double*. ...
      (comp.lang.c)