6.5.1 一般规定
IMPORTANT
这一小节是整个表达式章节的地基。尤其要注意“未定序求值”和“有效类型”两组规则,它们是大量未定义行为与别名问题的来源。
表达式是由运算符和操作数组成的序列。它可以:
- 计算一个值;
- 指代一个对象或函数;
- 产生副作用;
- 或者同时完成其中若干项。
一个运算符的各个操作数,其值计算都先于该运算符结果的值计算。
如果对同一个标量对象的某个副作用,与下列任一操作之间是未排序关系,则行为未定义:
- 对同一标量对象的另一个副作用;
- 使用该标量对象当前值的值计算。
若某个表达式的子表达式存在多种允许的求值顺序,只要其中任意一种顺序会产生这种未排序副作用冲突,整体行为就是未定义的。
标准给出的典型例子
c
i = ++i + 1;
a[i++] = i;1
2
2
这类写法未定义;而下面这样的写法是允许的:
c
i = i + 1;
a[i] = i;1
2
2
运算符和操作数如何分组,由语法决定。语法同时给出了优先级层次;本章的大节顺序,基本就是从高优先级到低优先级。
除非标准后文另有规定,子表达式之间的副作用和值计算通常是未定序的。
按位运算符
~、<<、>>、&、^、|都要求整数类型操作数。它们的结果与整数内部表示有关,因此对有符号类型会涉及实现定义或未定义的方面。如果表达式求值过程中出现异常条件,例如:
- 结果在数学上无定义;
- 结果超出其类型的可表示范围;
则行为未定义。
有效类型
对一个对象已存值的访问,其有效类型通常是该对象的声明类型。
对没有声明类型的对象:
- 若通过某个非非原子字符类型左值写入值,则该左值类型会成为该对象后续访问的有效类型;
- 若通过
memcpy、memmove或字符类型数组复制值,则复制来源对象的有效类型会传递给目标对象; - 其他访问场景下,对象的有效类型就是用于访问它的左值类型。
一个对象的已存值,只能通过以下类型的左值访问:
- 与该对象有效类型兼容的类型;
- 与其兼容类型对应的限定版本;
- 与其底层类型兼容的有符号或无符号类型;
- 与上述底层类型对应限定版本兼容的有符号或无符号类型;
- 包含上述某种成员类型的聚合类型或联合体类型;
- 字符类型。
WARNING
这就是通常所说的“严格别名”约束来源。不是“只要内存布局看起来一样就能随便换类型读”。
浮点表达式收缩
浮点表达式可以被收缩(contracted),也就是把多步运算当作单个运算来求值,从而省略源码中本来会发生的中间舍入。
<math.h>中的FP_CONTRACTpragma 可以禁止这种收缩;否则,是否收缩以及怎样收缩,都由实现定义。涉及十进制浮点类型的运算,应按 ISO/IEC 60559 的语义求值,包括优选量子指数的结果规则。