ISO/IEC 9899:2011 条款6.5.16——赋值操作符
6.5.16 赋值操作符
语法
1、assignment-expression:
conditional-expression
unary-expression assignment-operator assignment-expression
assignment-operator: 以下之一
= *= /= %= += -= <<= >>= &= ^= |=
约束
2、一个赋值操作符应该具有一个可修改的左值作为其左操作数。
语义
3、一个赋值操作符将一个值存储在由左操作数所指派的对象中。一个赋值表达式在赋值后具有左操作数的值,[注:允许实现读此对象以确定该值,但不要求这么做,即使对象是用volatile限定的类型。]但并不是一个左值。赋值表达式的类型是左操作数的类型,在左值转换后即可具有。更新左操作数所存放的值的副作用顺序在左和右操作数的值计算之后。操作数的计算是没有顺序的。
6.5.16.1 简单赋值
约束
1、要遵守下列规则之一:[注:对于这些关于类型限定符的非对称的出现是由于将左值变为“表达式的值”并移除了任一类型限定符的转换(在6.3.2.1中指定)被应用于表达式的类型类别(比如,int volatile * const移除了const,但并不移除volatile)。]
——左操作数具有原子的、限定的、或非限定的算术类型,而右操作数具有算术类型;
——左操作数具有与右操作数相兼容的一个原子的、限定的、或非限定版本的一个结构体或联合体类型;
——左操作数具有原子的、限定的、或非限定的指针类型,且(考虑到左操作数在左值转换之后将会有的类型)两个操作数都具有指向限定或非限定版本的兼容类型的指针,而被左操作数所指向的类型具有被右值所指向的类型的所有限定符。
——左操作数具有原子的、限定的、或非限定的类型,且其中一个操作数是一个指向一个对象类型的指针,而另一个是一个指向一个限定或非限定版本的void的指针,而被左操作数所指向的类型具有被右操作数所指向类型的所有限定符。
——左操作数是一个原子的、限定的、或非限定的指针,而右操作数是一个空指针常量;或
——左操作数具有原子的、限定的、或非限定的_Bool,而右操作数是一个指针。
语义
2、在简单赋值(=)中,右操作数的值被转换为赋值表达式的类型,并替换由左操作数所指派的对象中的值。
3、如果正要存在一个对象中的值从另一个对象被读,两者以任一种形式叠交,那么该叠交应该是准确的,且两个对象应该具有一个兼容类型的限定或非限定版本;否则,行为是未定义的。
4、例1 在下列程序片段中
int f(void); char c; /* ... */ if( (c = f()) == -1 ) /* ... */
由函数所返回的int值可以被截断,当存储在char中时,然后在比较之前被转换回int宽度。在某一实现中,其“平凡的”char具有与unsigned char具有相同范围(并且char比int要窄),那么转换结果不能是负数,这样比较的操作数可能永远不会比较为相等的。从而,对于充分的可移植性,变量c应该被声明为int。
5、例2 在下列程序片段中:
char c; int i; long l; l = (c =i);
i的值被转换为赋值表达式c = i的类型,即char类型。在圆括号中括起来的表达式的值然后被转换为外部赋值表达式的类型,即long int类型。
6、例3 考虑以下程序片段
const char **cpp; char *p; const char c = 'A'; cpp = &p; // 违背了约束 *cpp = &c; // 有效 *p = 0; // 有效
第一个赋值是不安全的,因为它可能允许以下有效代码企图尝试改变const对象c的值。
6.5.16.2 复合赋值
约束
1、仅对于操作符 += 以及 -=,要么左操作数应该是指向一个完整对象类型的一个原子的、限定的或非限定的指针,并且右操作数应该具有整数类型;要么左操作数应该具有原子的、限定的或非限定的算术类型,且右操作数应该具有算术类型。
2、对于其它操作符,左操作数应该具有原子的、限定的、或非限定的算术类型,并且(考虑在左值转换之后左操作数将会具有的类型)每个操作数应该具有与那些被相应二进制操作符所允许的相一致的算术类型。
语义
3、一个形式为 E1 op= E2 的复合赋值等价于简单的赋值表达式 E1 = E1 op (E2),除了左值E1仅被计算一次,并且关于一个不确定顺序的函数调用,一个复合赋值的操作是一单个计算。如果E1是一个原子类型,那么复合赋值是一个带有memory_order_seq_cst存储器次序语义的读-修改-写操作。[注:这里,一个指向原子对象的指针可以是一种形式,且E1和E2具有整数类型,这个等价于下列代码次序,其中T1是E1的类型,T2是E2的类型:
T1 *addr = &E1; T2 val = (E2); T1 old = *addr; T1 new; do { new = old op val; } while(!atomic_compare_exchange_strong(adds, &old, new));
这里,new作为操作的结果。
如果E1或E2具有浮点类型,那么在丢弃的对new的计算期间所遭遇的异常情况或浮点异常应该被丢弃,为了满足对E1 op= E2与E1 = E1 op (E2)的等价性。比如,如果附录F是有效的,所涉及的浮点类型具有IEC 60559格式,并且FLT_EVAL_METHOD为0,那么等价的代码将会是:
#include <fenv.h> #pragma STDC FENV_ACCESS ON /* ... */ fenv_t fenv; T1 *addr = &E1; T2 val = E2; T1 old = *addr; T1 new; feholdexcept(&fenv); for(;;) { new = old op val; if(atomic_compare_exchange_strong(adds, &old, new)) break; feclearexcept(FE_ALL_EXCEPT); } feupdateenv(&fenv);
如果,FLT_EVAL_METHOD不为0,那么T2必须是一个具有E2被计算的范围与精度的类型,为了满足等价性。
]