43

I'm reading this blog post and under the section Null pointer constants and parenthesized expressions the author references § 6.3.2.3 and § 6.5.1 from the ISO C standard and says:

It doesn't say that a parenthesized null pointer constant is a null pointer constant.

Which implies, strictly speaking, that (void*)0 is a null pointer constant, but ((void*)0) is not.

Then:

I'm sure that most C implementations do treat a parenthesized null pointer constant as a null pointer constant, and define NULL either as 0, ((void*)0), or in some other manner.

The two referenced sections say:

§ 6.3.2.3

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

§ 6.5.1

A parenthesized expression is a primary expression. Its type and value are identical to those of the unparenthesized expression. It is an lvalue, a function designator, or a void expression if the unparenthesized expression is, respectively, an lvalue, a function designator, or a void expression.

Doesn't the bolded sentence contradict the author's claim that ((void*)0) is not a null pointer constant?

user4164058
  • 545
  • 1
  • 4
  • 10

3 Answers3

34

Doesn't the bolded sentence contradict the author's claim that ((void*)0) is not a null pointer constant?

No, it doesn't. (I confess to being a bit biased, since the referenced blog is mine.)

The bolded sentence says that its type and value are identical to those of the unparenthesized expression. That's not enough to imply that it's a null pointer constant.

Consider:

void *var = 0;

(void*)0 is a null pointer constant. ((void*)0) has the same type and value as (void*)0. var also has the same type and value as (void*)0, but var clearly is not a null pointer constant.

Having said that, I'm 99+% sure that the intent is that ((void*)0) is a null pointer constant, and more generally that any parenthesized null pointer constant is a null pointer constant. The authors of the standard merely neglected to say so. And since the description of parenthesized expressions in 6.5.1p5 specifically enumerates several other characteristics that are inherited by parenthesized expressions:

A parenthesized expression is a primary expression. Its type and value are identical to those of the unparenthesized expression. It is an lvalue, a function designator, or a void expression if the unparenthesized expression is, respectively, an lvalue, a function designator, or a void expression.

the omission is troubling (but only mildly so).

But let's assume, for the sake of argument, that ((void*)0) is not a null pointer constant. What difference does it make?

(void*)0 is a null pointer constant, whose value is a null pointer of type void*, so by the semantics of parenthesized expressions ((void*)0) also has a value that is a null pointer of type void*. Both (void*)0 and ((void*)0) are address constants. (Well, I think they are.) So what contexts require a null pointer constant and do not accept an address constant? There are only a few.

6.5.9 Equality operators

An expression of function pointer type may be compared for equality to a null pointer constant. (An object pointer may be compared to an expression of type void*, but a function pointer may not, unless it's a null pointer constant.) So this:

void func(void);
if (func == ((void*)0)) { /* ... */ }

would be a constraint violation.

6.5.16.1 Simple assignment

In an assignment, a null pointer constant may be assigned to an object of pointer-to-function type, and will be implicitly converted. An expression of type void* that's not a null pointer constant may not be assigned to a function pointer. The same constraints apply to argument passing and initialization. So this:

void (*fp)(void) = ((void*)0);

would be a constraint violation if ((void*)0) were not a null pointer constant. Thanks to commenter hvd for finding this.

7.19 Common definitions <stddef.h>

The macro NULL expands to "an implementation-defined null pointer constant". If ((void*)0) is not a null pointer constant, then this:

#define NULL ((void*)0)

would be invalid. This would be a restriction imposed on the implementation, not on programmers. Note that this:

#define NULL (void*)0

is definitely invalid, since macro definitions in standard headers must be fully protected by parentheses where necessary (7.1.2p5). Without the parentheses, the valid expression sizeof NULL would be a syntax error, expanding to sizeof (void*) followed by an extraneous constant 0.

UPDATE: The upcoming 2023 ISO C standard (C23) corrects this problem and makes it clear that ((void*)0) is a null pointer constant.

The N3054 draft, 6.5.1 paragraph 5, says:

A parenthesized expression is a primary expression. Its type, value, and semantics are identical to those of the unparenthesized expression.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • One detraction: making your argument run through `void* var` has the weakness that `var` is not a null pointer constant because it also happens not to be an _ICE_. – Iwillnotexist Idonotexist Oct 21 '14 at 00:50
  • 2
    Can you elaborate a bit on what "not a null pointer constant" means? I mean, `var` isn't one because it isn't a constant, but what does it mean for `((void*)0)` not to be one? Is there any sort of perverse compiler behavior that is technically allowed by the spec due to this oversight? – ruakh Oct 21 '14 at 00:50
  • 2
    @KeithThompson 6.3.2.3 Clause 4: _Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal._ – Iwillnotexist Idonotexist Oct 21 '14 at 00:54
  • Just musing, the previous sentence says "if a null pointer constant is converted to a pointer type, the resulting pointer [is] called a null pointer". A strict reading might suggest that `weird` need not be a null pointer at all, if that conversion does not happen because `((void*)0)` is not a null pointer constant in the first place. And if `weird` is not a null pointer, then it need not compare equal to one. Naturally this is all nonsense if the act of adding parentheses to `(void*)0` itself constitutes a conversion to a pointer type, which it likely does if `((void*)0)` is of pointer type. – Crowman Oct 21 '14 at 01:09
  • In other words, if `((void*)0)` is *not* a null pointer constant, but it *is* a null pointer, then 6.3.2.3.4 would seem to suggest that all is well, since you can always convert it to another pointer type. – Crowman Oct 21 '14 at 01:15
  • @PaulGriffiths: If it isn't a null pointer constant, it certainly is an address constant with null pointer value, which is usable all the same places. – Ben Voigt Oct 21 '14 at 01:17
  • @PaulGriffiths Seconding Ben, that's outrageous; We can all agree that `void *weird = ((void*)0);` makes `wierd` a null pointer, because even if `((void*)0)` were not a _null pointer constant_ (which I believe it is) then it must be a valid null pointer initializer, because _"Its type and value are identical to those of the unparenthesized expression"_. And because the unparenthesized expression will succeed in making `wierd` a `NULL` pointer, so will the parenthesized one. – Iwillnotexist Idonotexist Oct 21 '14 at 01:23
  • @IwillnotexistIdonotexist: Sure, but the question seems to be how do you get to that conclusion from the wording in the standard? I don't think anyone is suggesting C is intended to work in some completely weird way where the presence or absence of parentheses completely transforms the behavior of assignment, just whether there's some technical omission in the words. – Crowman Oct 21 '14 at 01:28
  • 2
    Merely converting `((void*)0)` to a pointer-to-function type is enough to demonstrate the problem, even without any comparisons: `void (*fp)(void) = ((void*)0);` is valid if and only if `((void*)0)` is a NPC. Perhaps also interesting is that C++ has replaced the semantics of parentheses with a much simpler one: "The parenthesized expression can be used in exactly the same contexts as those where the enclosed expression can be used, and with the same meaning, except as otherwise indicated." This also affects, for example, `char str[] = ("abc");`, which is valid in C++ but not in C. –  Oct 21 '14 at 09:13
  • @hvd: Good catch; I've added that to my answer. (I blame the clumsy search functionality of my PDF viewer.) – Keith Thompson Oct 21 '14 at 14:14
  • Are there any cases other than `sizeof NULL` where the lack of parentheses on `(void*)0` would be a problem? Is there any reason `sizeof NULL` should be expected to have any particular meaning, since different kinds of pointers are allowed to have different-sized representations? – supercat Aug 14 '17 at 23:39
  • @supercat: In practice, there are no cases where the lack of parentheses is a problem, because no conforming implementation uses `#define NULL (void*)0`. I consider it fortunate that the authors of the standard do not arbitrarily loosen requirements because they presume that some particular construct would not be useful or meaningful, particularly when those requirements impose essentially zero cost. Can you think of any reason, even if it were allowed, to have `#define NULL (void*)0` rather than `#define NULL ((void*)0)`? – Keith Thompson Aug 14 '17 at 23:53
  • @KeithThompson: Having NULL be a null pointer constant would be a useful one, would it not? – supercat Aug 15 '17 at 00:23
  • @supercat: What? `NULL` *is* a null pointer constant, by definition. I'd advocate fixing the description of parenthesized expressions to make it clear that `((void*)0)` is a valid definition for `NULL`. Changing the standard to permit `(void*)0` as a valid definition for `NULL` would be silly. – Keith Thompson Aug 15 '17 at 00:29
  • @KeithThompson: In what cases would `(void*)0` be observably different from `((void*)0)`? On systems where different kinds of pointers have different sizes, having a compiler squawk at `sizeof NULL` would seem better than having it report something that may not be the size one is interested in. In what other context would `(void*)0` behave observably differently from `((void*)0)`? – supercat Aug 15 '17 at 01:21
  • @supercat: I do not care. A conforming compiler must handle `sizeof NULL` -- and as far as I know, existing compilers don't have a problem with it. That doesn't mean you have to use it in your code. Compilers can warn about anything they like. – Keith Thompson Aug 15 '17 at 19:05
  • @KeithThompson: For what purposes would a non-contrived program want to use an expression that could yield either the size of an "int" or the size of a "void*"? – supercat Aug 15 '17 at 19:45
7

It is a parenthesized expression which contains a null pointer constant, so it indisputably is a null pointer value. Using it as an rvalue has exactly the same effect as using the "compliant" version as an r-value.

If there were some syntactic rules that could only accept a null pointer constant, it would not qualify. But I'm not aware of any (though I'm less expert on C).

And while neither one is a constant (referring to the formal grammar production), both can appear in a constant expression in an initializer, because both null pointer constants and address constants are allowed, and a constant null pointer value is explicitly included in the category of address constant.

Pointer comparisons also specifically mention null pointer constants... but here pointer values are also accepted, and all null pointer values are treated equally. Same for the ternary and assignment operators.

Please do be aware that these rules are quite different in C++, where both the above expressions are constant null pointer values of type void*, but not universal null pointer constants. Null pointer constants in C++ are integral constant expressions which evaluate to zero. And void* doesn't implicitly convert to other pointer types.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • How is "neither [`(void*)0` or `((void*)0)`] one a constant"? The standard claims explicitly that `(void*)0` is a null pointer constant. – Iwillnotexist Idonotexist Oct 21 '14 at 01:08
  • 1
    @IwillnotexistIdonotexist: There is a grammar production for *constant* which includes only *integer-constant*, *floating-constant*, *enumeration-constant*, and *character-constant*. These pointer values are constant expressions, but not constants. Your concept that the word "constant" encompasses every sort of integer constant, address constant, null pointer constant, and so on just doesn't hold here; the usual rules of the English language do not apply, the formal grammar does. – Ben Voigt Oct 21 '14 at 01:11
  • Perhaps you could then disambiguate that use of _constant_ as "And while neither one is a _syntactical_ constant, both can..."? – Iwillnotexist Idonotexist Oct 21 '14 at 01:25
  • 2
    It turns out there is at least one context where a null pointer constant is permitted but `((void*)0)`, if it weren't an NPC would not be. An expression of pointer-to-function type may be compared for equality or inequality to a null pointer constant, but not to any other constant expression of type `void*`. See my updated answer for details. – Keith Thompson Oct 21 '14 at 04:59
-4

Try printing below line in your C code:

printf("%p",(void*)0);

You will get the output as:

(nil)

  • 1
    Which you'll also get for `(char*)0`, or `(void*){0}` but `(char*)0` and `(void*){0}` aren't null pointer constants while `(void*)0` is. – Petr Skocik Apr 11 '19 at 12:19