Model of constant expressions in C99 ==================================== This model describes how to determine whether an expression in C99 is a constant expression, and what types of constant expression it is. Address constants and address constants plus or minus integer constant expressions are treated as a single type of constant expression, as discussed in my analysis of issues with constant expressions. This model is intended to be consistent with C99 but may not be strictly minimal in it. Expressions are considered to have the following boolean attributes, the values of which are determined as described below. bad_operator The expression contains a prohibited operator. overflow The expression (or a subexpression) is outside the range of its type. int_operands All operands are permitted for integer constant expressions. arith_operands All operands are permitted for arithmetic constant expressions. arith_bad_cast The expression contains a cast that prevents it from being an arithmetic constant expression. float_constant The expression is a floating constant. has_const_addr The expression has a constant address. addr_const_expr The expression is an address constant. arith_const_ok The expression does not involve the evaluation of anything that would stop it from being an arithmetic constant expression. arith_const_expr The expression is an arithmetic constant expression. int_const_expr The expression is an integer constant expression. null_pointer_const The expression is a null pointer constant. (arith_bad_cast and arith_const_ok, not needed for C90, are needed for C99 because arithmetic constant expressions may contain sizeof expressions whose results are not integer constants, as long as they do not contain casts other than those between arithmetic types. Thus having suitable operands does not mean that the expression is in fact constant.) The values of these are described using the following notation: E. := value sets the given attribute. E.attrs := value sets all the attributes to a default. E.attrs := A.attrs sets all the attributes from those of part of the syntax production for E. E.attrs := merge(A.attrs, B.attrs) merges some of the attributes from those of two parts of the syntax production in the following way, and merging more attributes is defined by analogy. E.bad_operator := A.bad_operator || B.bad_operator E.overflow := A.overflow || B.overflow E.int_operands := A.int_operands && B.int_operands E.arith_operands := A.arith_operands && B.arith_operands E.arith_bad_cast := A.arith_bad_cast || B.arith_bad_cast E.arith_const_ok := A.arith_const_ok && B.arith_const_ok E.float_constant := false E.has_const_addr and E.addr_const_expr must be set explicitly after a merge. Some of the attributes are determined in a fixed way for all expressions, after the other attributes have been determined as described below: arith_const_expr := (of arithmetic type) && arith_operands && arith_const_ok && !arith_bad_cast && !bad_operator && !overflow int_const_expr := (of integer type) && int_operands && !bad_operator && !overflow Except for casts and parenthesised expressions (described below): null_pointer_const := int_const_expr && (value is 0) Others are determined as described below. "Documentation only." means that if the constraints are satisfied then this assignment has no effect. Conversion of arrays and function designators to pointers is considered to be an explicit operation for the purposes of this model. offsetof expressions are considered a specific type of primary-expression. Type names are considered to have attributes derived by merging those of the full expressions (i.e., array dimensions) appearing in the type name. A merge of an empty set of attribute lists has effect by setting to true those attributes a normal merge sets with "&&" and to false those a normal merge sets with "||". An initializer list has attributes derived by merging those of the initializers in it. The model ========= Conversion of arrays to pointers: E : A (where A is an array being implicitly converted to a pointer) E.attrs := A.attrs E.has_const_addr := false E.addr_const_expr := A.has_const_addr Conversion of function designators to pointers: E : F (where F is a function designator being implicitly converted to a pointer) E.attrs := A.attrs E.has_const_addr := false E.addr_const_expr := F.has_const_addr Primary expressions: E : integer-constant E : enumeration-constant E : character-constant E : offsetof(type, member-designator) E.bad_operator := false E.overflow := false E.int_operands := true E.arith_operands := true E.float_constant := false E.arith_bad_cast := false E.arith_const_ok := true E.has_const_addr := false E.addr_const_expr := false E : floating-constant E.bad_operator := false E.overflow := false E.int_operands := false E.arith_operands := true E.float_constant := true E.arith_bad_cast := false E.arith_const_ok := true E.has_const_addr := false E.addr_const_expr := false E : identifier E : string-literal If the expression is a string literal, or an identifier which designates a function or an object of static storage duration: E.bad_operator := false E.overflow := false E.int_operands := false E.arith_operands := false E.float_constant := false E.arith_bad_cast := false E.arith_const_ok := false E.has_const_addr := true E.addr_const_expr := false Otherwise: E.bad_operator := false E.overflow := false E.int_operands := false E.arith_operands := false E.float_constant := false E.arith_bad_cast := false E.arith_const_ok := false E.has_const_addr := false E.addr_const_expr := false E : ( E1 ) E.attrs := E1.attrs (There is the question of whether parentheses have operands or are purely syntactic, but this seems the best interpretation. C90 lists parentheses as both operators and punctuators, so there is no resolution from that distinction.) Postfix operators: E : E1 [ E2 ] Without loss of generality suppose E1 is the argument with pointer type and E2 is the argument with integer type. E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := E1.addr_const_expr && E2.int_const_expr E.addr_const_expr := false E : E1 (optional list E2, E3, ...) E.attrs := merge(E1.attrs, E2.attrs, ...) E.bad_operator := true E.arith_const_ok := false E.has_const_addr := false E.addr_const_expr := false E : E1 . identifier E.attrs := E1.attrs E.arith_const_ok := false /* Documentation only. */ E.float_constant := false /* Documentation only. */ E.addr_const_expr := false /* Documentation only. */ E : E1 -> identifier E.attrs := E1.attrs E.arith_const_ok := false /* Documentation only. */ E.float_constant := false /* Documentation only. */ E.has_const_addr := E1.addr_const_expr E.addr_const_expr := false E : E1 ++ E : E1 -- E.attrs := E1.attrs E.bad_operator := true E.arith_const_ok := false /* Documentation only. */ E.float_constant := false /* Documentation only. */ E.has_const_addr := false E.addr_const_expr := false E : ( T ) { I } E : ( T ) { I , } E.attrs := merge(T.attrs, I.attrs) E.arith_const_ok := false E.has_const_addr = (true if outside the body of a function, false otherwise) E.addr_const_expr := false Unary operators: E : ++ E1 E : -- E1 As postfix ++ and -- above. E : sizeof E1 E : sizeof ( T ) If the type is a VLA type: E.attrs := (T or E1, as applicable).attrs E.arith_operands := true E.arith_const_ok := false E.float_constant := false /* Documentation only. */ E.has_const_addr := false E.addr_const_expr := false Otherwise, as integer-constant above. E : & E1 E.attrs := E1.attrs E.arith_const_ok := false /* Documentation only. */ E.float_constant := false /* Documentation only. */ E.has_const_addr := false E.addr_const_expr := E1.has_const_addr E : * E1 E.attrs := E1.attrs E.arith_const_ok := false /* Documentation only. */ E.float_constant := false /* Documentation only. */ E.has_const_addr := E1.addr_const_expr E.addr_const_expr := false E : + E1 E : - E1 E : ~ E1 E : ! E1 E.attrs := E1.attrs E.float_constant := false E.has_const_addr := false E.addr_const_expr := false If E1.arith_const_expr and signed arithmetic overflows for - or forms a trap representation for ~ then: E.overflow := true Cast operators: E : ( T ) E1 E.attrs := merge(T.attrs, E1.attrs) E.has_const_addr := false E.addr_const_expr := (E1.int_const_expr || E1.addr_const_expr) && (type is a pointer type) If E1.float_constant and the type is an integer type: E.int_operands := true If the type of the cast is not an integral type or the type of E1 is not an arithmetic type: E.int_operands := false If the type of the cast is not an arithmetic type or the type of E1 is not an arithmetic type: E.arith_operands := false E.arith_const_ok := false E.arith_bad_cast := true If E1.arith_const_expr and the conversion involves undefined behavior: E.overflow := true After setting E.int_const_expr: E.null_pointer_const := (E.int_const_expr && (value of E is 0)) || (E1.int_const_expr && (value of E1 is 0) && (type is void *)) Multiplicative operators: E : E1 * E2 E : E1 / E2 E : E1 % E2 E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := false E.addr_const_expr := false If E1.arith_const_expr and E2.arith_const_expr and the signed arithmetic overflows or involves division by zero: E.overflow := true Additive operators: E : E1 + E2 E : E1 - E2 E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := false E.addr_const_expr := (E1.addr_const_expr && (E1 does not point to a variable-length type) && E2.int_const_expr) || (E2.addr_const_expr && (E2 does not point to a variable-length type) && E1.int_const_expr) If E1.arith_const_expr and E2.arith_const_expr and the signed arithmetic overflows: E.overflow := true Bitwise shift operators: E : E1 << E2 E : E1 >> E2 E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := false E.addr_const_expr := false If E1.arith_const_expr and E2.arith_const_expr and either the shift is by a negative amount, or the shift is by an amount greater than or equal to the width of the promoted left operand, or the shift overflows in signed arithmetic (i.e. forms a trap representation), or the signed left shift is undefined: E.overflow := true Relational and equality operators: E : E1 < E2 E : E1 > E2 E : E1 <= E2 E : E1 >= E2 E : E1 == E2 E : E1 != E2 E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := false E.addr_const_expr := false Bitwise AND, exclusive OR and inclusive OR operators: E : E1 & E2 E : E1 ^ E2 E : E1 | E2 E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := false E.addr_const_expr := false If E1.arith_const_expr and E2.arith_const_expr and the signed arithmetic overflows (i.e. forms a trap representation): E.overflow := true Logical AND operator: E : E1 && E2 E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := false E.addr_const_expr := false If E1.arith_const_expr and E1 has value 0: E.overflow := false E.arith_const_ok := true E.bad_operator := false Logical OR operator: E : E1 || E2 E.attrs := merge(E1.attrs, E2.attrs) E.has_const_addr := false E.addr_const_expr := false If E1.arith_const_expr and E1 does not have value 0: E.overflow := false E.arith_const_ok := true E.bad_operator := false Conditional operator: E : E1 ? E2 : E3 E.attrs := merge(E1.attrs, E2.attrs, E3.attrs) E.has_const_addr := false If !E1.arith_const_expr: E.addr_const_expr := false If E1.arith_const_expr, let En be E2 if E1 is nonzero, E3 if E1 is zero. Then: E.addr_const_expr := En.addr_const_expr E.overflow := En.overflow E.arith_const_ok := En.arith_const_ok E.bad_operator := En.bad_operator Assignment operators: E : E1 assignment-operator E2 E.attrs := merge(E1.attrs, E2.attrs) E.bad_operator := true E.has_const_addr := false E.addr_const_expr := false E.arith_const_ok := false Comma operator: E.attrs := merge(E1.attrs, E2.attrs) E.bad_operator := true E.has_const_addr := false E.addr_const_expr := false