(This version is expanded and revised from those previously sent to
some mailing lists and comp.std.c.)
Problems with constant expressions in C99
=========================================
(1) There are the problems in DR#261 the UK C Panel submitted a couple
of years ago. This is where the Oxford minutes say "DR 261. Clive
had potential sticking points and will converse with interested
parties." and "ACTION: CF to draft words describing the four uses of
the term constant to be considered in the response to DR 261. DONE
merged in here or a separate document? I don't have them.".
The actual wording proposed in that DR about "translation-time values"
is problematic because expressions with translation-time values don't
seem to be restricted to those that would be *integer* constant
expressions. Null pointer constants require *integer* constant
expressions. Arrays require *integer* constant expressions not to be
VLAs, but *any* type of constant expression suffices to require the
constraint on the size being > 0 to be checked (this may be a defect,
but should be separately noted if being fixed in the course of fixing
another defect).
int x[(int)(double)1.0];
is a VLA while
int x[(int)(double)0.0];
int y[(int)-1.0];
are both constraint violations, although the expressions are only
arithmetic constant expressions (presuming no extensions to make them
integer constant expressions). Problems with this arise because in
general it is not possible for an arithmetic constant expression to be
evaluated at translation time: if __STDC_IEC_559__ is defined and the
FENV_ACCESS pragma is on, the execution-time rounding mode may affect
the value of the expression (F.7.4). Discussions at a UK C Panel
meeting suggested that the problem might be that such expressions are
still considered constant expressions in the presence of the pragma,
and that some wording that apparently was written on the blackboard at
Oxford to deal with DR#261 might help.
(2) The UK C Panel couldn't agree when we discussed the issue about
whether the changes in C99 (where comma expressions may appear in an
unevaluated subexpression of a constant expression outside sizeof -
including possibly in the preprocessor) were intended or not (so that
draft DR didn't get submitted). This may not be a defect, but the
effect is that there are in effect several flags on subexpressions
(and on types appearing within expressions) that must maintained by an
implementation in order to determine whether an expression is a
constant expression and, if so, what sort of constant expression it
is; some non-constant subexpressions are OK in integer constant
expressions if they are within an unevaluated subexpression, while
some are only OK within sizeof. Furthermore, a sizeof expression
whose result is not an integer constant may be OK within an
unevaluated part of an arithmetic constant expression - as long as it
does not contain a cast of prohibited form - but not within an
unevaluated part of an integer constant expression. It isn't even
immediately obvious from the definitions that all integer constant
expressions are also arithmetic constant expressions (although I
believe this to be so).
(3) Address constants can contain casts to variably modified types, which
might be a defect .
If you cast to such a type (while keeping the expression in fact a
constant), any nonconstants directly in the sequence of tokens for the
type (as opposed to in a typedef) are also subject to the constraints
on what may only appear in unevaluated subexpressions, and probably to
those on permitted operands (see also item 5 below).
(4) When exactly can "other forms" of constant expressions (6.6
paragraph 10) be accepted, and what restrictions apply to such "other
forms"? One view on comp.std.c, e.g.
),
is that they can't be accepted as integer constant expressions (or one
of the other types enumerated), only as some type in addition to
those, that can be accepted in initializers (or, I suppose, allowed in
constraint checking for VLAs having positive size), and that any such
forms must follow the Syntax, Description and Constraints sections of
6.6. DR#032 suggests they can be accepted as integer constant
expressions, but still must satisfy the constraints. The precise
restrictions on "other forms" should be made much clearer. (With
regard to each type of constant expression, a given expression might
be required by the standard to be a constant expression of that type,
permitted to be such (as an "other form"), or prohibited to be such,
and the standard should make clear which of the three alternatives
applies in each case.)
(5) What are the "operands" (6.6 paragraphs 6 and 8) of a constant
expression? Presumably it means the "ultimate" operands in some
sense, recursing down various operators to their operands, but this
isn't specified. Presumably compound literals such as (const int){0}
are not meant to be constant expressions (being references to
anonymous variables), but it is hardly clear from the text that 0
isn't the operand here. When compound literals appear in sizeof
expressions whose results are not integer constants in unevaluated
parts of expressions, whether the expressions are arithmetic constant
expressions may depend on what casts are present in the compound
literals. Also, one would expect that parentheses are meant to be
purely syntactic rather than having "operands", so that (int)(0.0) is
an integer constant expression just as (int)0.0 is, and ((void *)0) is
a null pointer constant, but this is nowhere stated.
(6) We know from 6.5.3.4 paragraph 2 that sizeof of a non-VLA is an
integer constant. Is it also the case that sizeof of a VLA is *never* an
integer constant, or indeed never counts as a constant? Can it be shown
from the standard that sizeof(int [(int)(double)1.0]) isn't an arithmetic
constant expression - or is it?
(7) May address constants use array indices that are arithmetic
constant expressions but not integer constant expressions? May they
use such expressions as conditions in conditional expressions?
(8) 6.6 paragraph 7 says that a constant expression in an initializer
"shall be, or evaluate to" one of four possibilities. When does "be"
apply, and when does "evaluate to" apply? (Expressions *are* expressions
(pieces of source code), but *evaluate to* values (something more
abstract, but with no intersection with pieces of source code), so only
one can apply in each case.)
* an arithmetic constant expression - this is a type of expression, so
"be" applies.
* a null pointer constant - a type of expression, so "be" applies.
* an address constant - a type of value (with some associated restrictions
on the expression behind the value), so "evaluate to" applied.
* an address constant for an object type plus or minus an integer constant
expression - some sort of hybrid to which neither applies.
(The change made following C90 DR#145 is inadequate for this issue, as
the last listed form is still a strange compound of "be" and "evaluate
to".)
What of
static int x[10];
static int *p = 1 + x + 9 - 0 + 0;
? Is this valid? Pointing one past the end of an array, it clearly falls
outside the definition of "address constant". But the syntax doesn't make
it clearly syntactically of the form (address constant) +/- (integer
constant expression). What about the null pointer (void *)(void *)(int)0?
That's created by casting an integer constant expression to pointer type -
but the rules for address constants refer to "an integer *constant* cast
to pointer type".
I think a proper fix for this last issue is to specify all standard types
of constant expression syntactically, with a more precise (unified?)
definition of the last two.
(9) What is the significance of the constraint that "Each constant
expression shall evaluate to a constant that is in the range of
representable values for its type."? It clearly applies to floating
point arithmetic done in greater range than the underlying type, but
does it apply otherwise? Also, does the requirement apply in any way
to subexpressions? (The response to C90 DR#031 says so, but the
example given in a footnote shows this is only evaluated
subexpressions, not "each subexpression" as suggested in that DR.)
Supposing DBL_MAX + DBL_MAX is evaluated to a finite value using a
range bigger than that of double, is 0 * (DBL_MAX + DBL_MAX) a valid
arithmetic constant expression? Considering integer types, unsigned
arithmetic always yields results within the range of the type.
INT_MAX + 1, however, yields undefined behavior at runtime. Is it
nevertheless considered to have a value, outside the range of values
of int, and so required not to be a constant expression, or is it
unspecified whether it is a constant expression? What of 0 * (INT_MAX
+ 1)? What of 0 << (CHAR_BIT * sizeof(int))? Undefined behavior is
OK in unevaluated parts of constant expressions, but what about in
evaluated parts as here? What about a cast to signed integer type of
a value not representable in that type? If the implementation defines
this to raise a signal, is it then not an integer constant expression,
while if it gives an implementation-defined value is it then an
integer constant expression? What of (-1 << 0), which C99 changed
from implementation-defined to undefined? Does having undefined
behavior mean it is prohibited in static initializers?