To clarify existing practice, several varieties of constant expression have been identified:
The expression following #if (§3.8.1)  
    must expand to integer constants,
    character constants,
    the special operator defined,
    and operators with no side effects.  
    No environmental inquiries can be made,
    since all arithmetic is done as translate-time
    (signed or unsigned) long integers,
    and casts are disallowed.  
    The restriction to translate-time arithmetic frees an
    implementation from having to perform execution-environment
    arithmetic in the host environment. It does not preclude
    an implementation from doing so --- the implementation may
    simply define ``translate-time arithmetic'' to be that of the
    target.  
    Unsigned arithmetic is performed in these expressions
    (according to the default widening rules)  
    when unsigned operands are involved;
    this rule allows for unsurprising arithmetic involving very large
    constants (i.e, those whose type is unsigned long)  
    since they cannot be represented as long
    or constants explicitly marked as unsigned.  
    Character constants, when evaluated
    in #if expressions, may be interpreted in the source character
    set, the execution character set, or some other implementation-defined
    character set.  
    This latitude reflects the diversity of existing practice,
    especially in cross-compilers.  
An integral constant expression
    must involve only numbers knowable at translate time,
    and operators with no side effects.  
    Casts and the sizeof
    operator may be used to interrogate the execution environment.  
Static initializers include integral constant expressions, along with floating constants and simple addressing expressions. An implementation must accept arbitrary expressions involving floating and integral numbers and side-effect-free operators in arithmetic initializers, but it is at liberty to turn such initializers into executable code which is invoked prior to program startup (see §2.1.2.2); this scheme might impose some requirements on linkers or runtime library code in some implementations.
The translation environment must not produce a less accurate value for a floating-point initializer than the execution environment, but it is at liberty to do better. Thus a static initializer may well be slightly different than the same expression computed at execution time. However, while implementations are certainly permitted to produce exactly the same result in translation and execution environments, requiring this was deemed to be an intolerable burden on many cross-compilers.
#if
expressions to determine properties of
the execution environment may now get different answers.