Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When NULL is not all-zero-bits, is an all-zero-bit pointer value also 'false'?

I know C compilers aren't required to use all zeros for the bit representation of NULL, but they *are* required by the standard to make NULL evaluate to false in boolean contexts/comparisons. Hence the 2nd printf in the program below will always output false.

But what I want to know is: on systems where NULL is *not* all zeros, will a pointer value that *is* all zeros also evaluate to false in boolean contexts/comparisons? In other words, will the 1st printf in the program below ever output true?

Or asked in a slightly different way: can I rely on calloc to produce a pointer value that will always evaluate to false in boolean contexts/comparisons? The 1st answer to this question uses memset to clear the bits of a long* named y, then goes on to say that y==0 is UB because y may be a "trap representation" (whatever that is). calloc is also just clearing bits, so maybe o->p in the 1st printf is also UB?


#include <stdio.h> #include <stdlib.h> #include <assert.h>  typedef struct { void * p; } obj;  int main() {     obj * o = calloc(sizeof(obj), 1);     assert(o);  // assume successful allocation     printf("%s\n", o->p ? "true" : "false");  // 1st: could print "true"?  Is o->p UB?     o->p = NULL;     printf("%s\n", o->p ? "true" : "false");  // 2nd: always prints "false"     return 0; } 
like image 252
textral Avatar asked Aug 27 '20 06:08

textral


People also ask

IS null pointer the same as 0?

The null pointer constant is always 0. The NULL macro may be defined by the implementation as a naked 0 , or a cast expression like (void *) 0 , or some other zero-valued integer expression (hence the "implementation defined" language in the standard).

What value does a null pointer have?

A null pointer constant is an integer constant expression that evaluates to zero. For example, a null pointer constant can be 0, 0L , or such an expression that can be cast to type (void *)0 .

Why is a pointer with a value of 0 Not a valid address?

It's not even an address abstraction, it's the constant specified by the C standard and the compiler can translate it to some other number as long as it makes sure it never equals a "real" address, and equals other null pointers if 0 is not the best value to use for the platform.

What is a zero pointer?

Null pointer is a pointer which points nothing. Some uses of null pointer are: b) To initialize a pointer variable when that pointer variable isn't assigned any valid memory address yet. b) To pass a null pointer to a function argument when we don't want to pass any valid memory address.


2 Answers

typedef struct { void * p; } obj; obj * o = calloc(sizeof(obj), 1); assert(o);  // Let us set aside the case of a failed allocation printf("%s\n", o->p ? "true" : "false");  // 1st: could print "true" ? 

can I rely on calloc to produce a pointer value that will always evaluate to false in boolean contexts/comparisons?

No - output could be "true".*1.

The bit pattern of all zeros, as a pointer, may not be a null pointer.

7.22.3.2 The calloc function
2 The calloc function allocates space for an array of nmemb objects, each of whose size is size. The space is initialized to all bits zero.301)
Footnote 301) Note that this need not be the same as the representation of floating-point zero or a null pointer constant.


Example: An implementation may only have only a single null pointer encoding with a bit pattern of all ones. (void *)0 converts the all zeros bit pattern int 0 to an all ones void *. if (null_pointer) is always false, regardless of the bit pattern of the null pointer.


*1 Yet practically yes, output is always "false". Implementations are uncommon these days that do not use all zero bit pattern as a null pointer. Highly portable code would not assume this practicality. Consider an old or new novel system may use a zero bit pattern as a non-null pointer - and sadly break many a code base that assumes an all zero bit pattern is a null pointer.

like image 87
chux - Reinstate Monica Avatar answered Oct 04 '22 15:10

chux - Reinstate Monica


Background information

Consider the following places where the logical value of an expression is used, all taken from C18, my emphasis in bold italic:

  • 6.3.1.2 (Boolean type) p1: When any scalar value is converted to _Bool, the result is 0 if the value compares equal to 0; otherwise, the result is 1.

  • 6.5.3.3 (Unary arithmetic operators) p5: The result of the logical negation operator ! is 0 if the value of its operand compares unequal to 0, 1 if the value of its operand compares equal to 0. The result has type int. The expression !E is equivalent to (0==E).

  • 6.5.13 (Logical AND operator) p3: The && operator shall yield 1 if both of its operands compare unequal to 0; otherwise, it yields 0. The result has type int.

  • 6.5.14 (Logical OR operator) p3: The || operator shall yield 1 if either of its operands compare unequal to 0; otherwise, it yields 0. The result has type int.

  • 6.5.15 (Condtional operator) p4: The first operand is evaluated; there is a sequence point between its evaluation and the evaluation of the second or third operand (whichever is evaluated). The second operand is evaluated only if the first compares unequal to 0; the third operand is evaluated only if the first compares equal to 0; the result is the value of the second or third operand (whichever is evaluated), converted to the type described below.

  • 6.8.4.1 (The if statement) p2: In both forms, the first substatement is executed if the expression compares unequal to 0. In the else form, the second substatement is executed if the expression compares equal to 0. If the first substatement is reached via a label, the second substatement is not executed.

  • 6.8.5 (Iteration statements) p4: An iteration statement causes a statement called the loop body to be executed repeatedly until the controlling expression compares equal to 0. The repetition occurs regardless of whether the loop body is entered from the iteration statement or by a jump.

"E compares equal to 0" is equivalent to the C expression (E == 0), and "E compares unequal to 0" is equivalent to the C expression (E != 0). The constraints of the equality operators are given by:

  • 6.5.9 (Equality operators) p2: One of the following shall hold:
    • both operands have arithmetic type;
    • both operands are pointers to qualified or unqualified versions of compatible types;
    • one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void; or
    • one operand is a pointer and the other is a null pointer constant.

Regarding the semantics of the equality operators where at least one operand is a pointer:

  • 6.5.9 (Equality operators) p5: Otherwise, at least one operand is a pointer. If one operand is a pointer and the other is a null pointer constant, the null pointer constant is converted to the type of the pointer. If one operand is a pointer to an object type and the other is a pointer to a qualified or unqualified version of void, the former is converted to the type of the latter.

  • p6: Two pointers compare equal if and only if both are null pointers, both are pointers to the same object (including a pointer to an object and a subobject at its beginning) or function, both are pointers to one past the last element of the same array object, or one is a pointer to one past the end of one array object and the other is a pointer to the start of a different array object that happens to immediately follow the first array object in the address space.

Regarding null pointer constants:

  • 6.3.2.3 (Pointers) p3: An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant67). 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.

OP's questions

But what I want to know is: on systems where NULL is not all zeros, will a pointer value that is all zeros also evaluate to false in boolean contexts/comparisons?

Aside: NULL is a null pointer constant, not necessarily a null pointer (see 6.3.2.3p3 above where it could be an integer constant expression). What you really mean is a system where the bit representation of a null pointer is not all zeros.

Note: As pointed out by Eric Postpischil in the comments below, a system could have several bit representations of null pointer values, so we assume that none of them are all-zero bit representations for this question.

In order for the pointer value to evaluate to false in boolean contexts/comparisons, it must compare unequal to 0. In this context, it must compare unequal to a null pointer constant. By 6.5.9p5 above, the null pointer constant will be converted to the type of the pointer it is being compared to. By 6.5.9p6 above, a null pointer value will not compare equal to a non-null pointer value. So a non-null pointer value with all bits zero on a system where a null pointer value is not all bits zero will evaluate to true in a boolean context.

Or asked in a slightly different way: can I rely on calloc to produce a pointer value that will always evaluate to false in boolean contexts/comparisons?

No, you cannot rely on calloc (or memset with byte value 0) to produce a pointer value that will evaluate to false in boolean contexts. If a pointer value with an all-zero bit representation is not a null pointer value it will evaluate to true in boolean contexts.

like image 29
Ian Abbott Avatar answered Oct 04 '22 14:10

Ian Abbott