26

In what version(s) of the C standards (if any) is the following well-defined?

void foo(void) {
    char *nullPtr = NULL;
    &*nullPtr;
}

Note that I am not assigning the result to anything - the second line is a simple statement.

This should be a question with an obvious answer, but (as seemingly happens way too often on such questions) I have heard just as many people say the answer is "obviously undefined" as "obviously defined".

On a rather related note, what about the following? Should foo produce a read of c?

extern volatile char c;

void bar(void) {
    volatile char *nonnullptr = &c;
    &*nonnullptr;
}

(C++ version of the same question: Is &*NULL well-defined in C++?)

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
TLW
  • 1,373
  • 9
  • 22
  • 1
    @Ben Voigt -- note that OP seems to be interested in which C Standards allow this construct as well-defined and which don't. The linked duplicate does not appear to answer these details. – ad absurdum Aug 05 '18 at 04:58
  • @Ben-Voigt - the linked question only address C11, not previous versions of the standard. (My kingdom for a multinotify.) – TLW Aug 05 '18 at 05:45
  • 1
    Related: https://stackoverflow.com/questions/16732788/can-a-c-compiler-eliminate-a-volatile-local-var-that-is-not-read (some info about observable behavior of volatile) – user202729 Aug 05 '18 at 07:23

1 Answers1

46

While attempts to dereference a null pointer cause undefined behavior, so *nullPtr is illegal, &*nullPtr is perfectly well-defined. According to footnote 102 in the C11 Draft Standard:

Thus, &*E is equivalent to E (even if E is a null pointer),....

This is a result of the fact that, for the unary & operator (§6.5.3.2 ¶3):

If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted,....

The C99 Standard has the same language, but this does not appear in the C90 Standard, and my reading of that standard is that &*nullPtr would indeed cause undefined behavior in pre-C99 implementations.

From the C90 Standard (§6.3.2.3):

The result of the unary & (address-of) operator is a pointer to the object or function designated by its operand....

and:

The unary * operator denotes indirection.... If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

Curiously, I don't see any discussion of this change in the C99 Rationale, though I may just be not finding it.

ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • 4
    There are many situations where the C Standard says a general class of actions invoke UB, without bothering to enumerate specific cases where there would be an obvious meaning that quality implementations should recognize. If every compiler to date had treated `&*p` as yielding `p` without regard for whether `p` is null, the authors of the Standard might not have considered the possibility that an implementation might do otherwise. A difficulty here, I think, is that the Standard lacks terms describe... – supercat Aug 05 '18 at 18:24
  • 2
    ... what constructs like `&structArray[i++].member` or `structArray[i++].member = 2;` do with the sub-expression `structArray[i++].member`, and what the result of that action is (before `&` or `=` is applied). Evaluation of the sub-expression would read the contents of `member`, and that doesn't happen; ergo, the sub-expression is not "evaluated". Given that the term `lvalue` is used to refer to the *source-code* expression, I would say that the expression gets "resolve" to an "lref". While *evaluation* of `*p` when `p` is a null pointer would invoke UB, resolving the expression should not. – supercat Aug 05 '18 at 18:30
  • @supercat -- Are you suggesting that some real implementations (pre-C99) must have treated `&*NULL` roughly, thus leading the Standards committee to clarify the issue? Interesting comments about sub-expressions; there are cases where expressions are explicitly _not_ to be evaluated, e.g. `sizeof` except for VLAs, and operands to `sizeof`. I tend to operate under the assumption that all expressions are evaluated unless explicitly exempted by the Standard, but I need to revisit the Standard to see how accurate this position is.... – ad absurdum Aug 05 '18 at 20:51
  • 1
    @supercat -- There is a culture of reading the Standard as uncharitably as possible, as if the [DeathStation 9000](http://wikibin.org/articles/deathstation-9000.html) could arrive with the Endtimes any day now. Your comments about resolution vs. evaluation remind me of the removal of VLAs from C11: why can't they give us a VLA type to use even if definition of actual VLAs is disallowed? – ad absurdum Aug 05 '18 at 20:52
  • The actual expression `&*NULL` should be a constraint violation on many platforms, since the unary operator `*` cannot accept a a literal zero nor a `void*` as an argument. There are many cases where the evaluating the operand of `&` would yield UB, but the use of `&` should be defined, such as `int i; scanf("%d",&i);`. Recognizing that operands may be take types, lrefs, or some take values, and that operators perform as much computation on their operands as needed to produce what they need, would clarify a lot. – supercat Aug 05 '18 at 21:07
  • As for VLAs, I'd like to see the Standard define concepts of "Selectively Conforming" programs, and "Safely Conforming" and "Minimally Safely Conforming" implementations. The only requirement for the latter would be that if an implementation's documented environmental requirements are satisfied and the program has not invoked UB, but an implementation is unable to continue processing a program in the manner defined by the Standard, it must indicate its refusal via some Implementation-Defined means. *Note the allowance of refusal replaces the "One Program" rule.* – supercat Aug 05 '18 at 21:14
  • Supporting VLA *types* within a compiler is a lot of work, and many special-purpose compilers are unlikely to be used with any programs that would benefit from such types. Even if a compiler is unable to support VLA types, bitfields, floating-point math, or integer types beyond 16 bits, that doesn't mean it wouldn't be useful to have the Standard specify how it would process programs that don't use any of those things. The question of whether to *define* a feature should be independent of the question of whether it would be practical for *all* implementations to support it. – supercat Aug 05 '18 at 21:21
  • 1
    @supercat: Re `&*NULL` "should be a constraint violation": If you disallow that, it becomes problematic to write innocuous expressions like `&foo[i]`. As a programmer, I don't want to be forced into writing `foo + i` there, as it would be substantially less expressive of my intent. (Besides which, it would break the obvious expectation that "array indexing is just as good as pointer arithmetic" and result in much silly cargo-culting of the former into the latter.) – Kevin Aug 05 '18 at 21:55
  • @Kevin: `NULL` is a macro which may expand to `0` or `(void*)0`. Neither `*0` and `*(void*)0` is a valid sub-expression. Perhaps "should" wasn't the ideal term, since I don't think the constraint about the unary-`*` operand being a pointer to a complete type should apply if neither the result nor its size is accessed or evaluated, but the Standard as written does not exempt the operand of `&` from that constraint. – supercat Aug 05 '18 at 22:04
  • 1
    @supercat: What constraint is violated? The standard linked in this answer only requires (under "constraints") that "The operand of the unary \* operator shall have pointer type." It does not require that it be a "complete" pointer in the constraints section, but only in the semantics section below. So the only way around this is for the implementation to define `NULL` as `0` and then balk at using an uncast zero in that position, which is asinine but might technically be correct (i.e. if I see an implementation actually try that, I'm filing an erratum). – Kevin Aug 05 '18 at 22:20
  • @Kevin: Things have changed between C89 and C11, and it seems there are no longer constraints with `void*`. MSVC doesn't like using `&*` with `void`, but it's generally C89-with-extensions, and the ability to have `&` and `*` cancel was added later. None of the compilers I tried accept `&*0` as an expression yielding a null pointer, however. – supercat Aug 05 '18 at 23:55
  • @supercat: As a "regular programmer" rather than a language lawyer, I'm actually fine with `&*0` being a type error, just so long as you don't break `&*NULL` (e.g. by defining `NULL` as `0`). – Kevin Aug 06 '18 at 00:26