Model of constant expressions in C90 ==================================== This model describes how to determine whether an expression in C90 is a constant expression, and what types of constant expression it is. Address constants and address constants plus or minus integral 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 C90 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 integral constant expressions. arith_operands All operands are permitted for arithmetic constant expressions. 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_expr The expression is an arithmetic constant expression. int_const_expr The expression is an integral constant expression. null_pointer_const The expression is a null pointer 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.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 && !bad_operator && !overflow int_const_expr := (of integral 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. 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.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.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.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.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 integral 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.has_const_addr := false E.addr_const_expr := false E : E1 . identifier E.attrs := E1.attrs E.float_constant := false /* Documentation only. */ E.addr_const_expr := false /* Documentation only. */ E : E1 -> identifier E.attrs := E1.attrs 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.float_constant := false /* Documentation only. */ E.has_const_addr := false E.addr_const_expr := false Unary operators: E : ++ E1 E : -- E1 As postfix ++ and -- above. E : sizeof E1 E : sizeof ( type-name ) As integer-constant above. E : & E1 E.attrs := E1.attrs 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.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 : ( type-name ) E1 E.attrs := E1.attrs E.float_constant := false E.has_const_addr := false E.addr_const_expr := E1.addr_const_expr && (type is a pointer type) (Note that C90 does not provide for integer constants case to pointer type to be address constants.) If E1.float_constant and the type is an integral 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 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 && E2.int_const_expr) || (E2.addr_const_expr && 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): 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 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 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 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 Comma operator: E.attrs := merge(E1.attrs, E2.attrs) E.bad_operator := true E.has_const_addr := false E.addr_const_expr := false