operator=
在C和C++中不是序列点,这个设计有什么好的理由吗?
我很难想出一个反例。
按照要求翻译如下:
一般来说,对于一个序列点需要一个理由才能使其成为序列点。而它们不需要理由就可以不成为序列点;这是默认的。
例如,&&
必须是序列点,因为存在短路行为:如果左侧为 false,则右侧不能被评估。(这不仅仅涉及优化问题;右侧可能具有副作用,并且/或者依赖于左侧为 true,例如 ptr && ptr->data
)。因此,必须先评估左侧,再评估右侧,以确定是否应该评估右侧。
对于 =
,不存在这样的理由,因为虽然双方都需要进行“评估”(虽然对双方的出现有不同的限制:左侧必须是左值 - 顺便说一下,“l”不代表“left”,而代表“location”,即在内存中的位置 - 我们不能将其赋值给临时变量或文字),但无论哪一方先进行评估都没有关系——只要在实际分配之前评估两个方面就可以了。
myArray[i++] = <包含i的表达式>;
,然后两个编译器中的一个在计算RHS之前计算了LHS,导致得出了错误的答案。虽然最终是我的错,但是这确实很棘手。 - j_random_hacker(((ch = *x++) != 0) && (*y++ = ch))
可以在没有&&
强制要求序列点的情况下延迟增加x的值,直到执行表达式的第二部分之后,因为编译器可以在增加x的值之前确定&&
的左操作数是否为零。实际上,序列点禁止的任何代码重排在实践中都不太可能有用,但在某些理论情况下可能会有用。 - supercatx = y[x] = 0
这行代码会首先对 y[x]
进行求值(使用旧的 x 值),然后再把 x
设为 0。 - Ariely[x] = 0
的值,因为该值只是右侧表达式(或其隐式转换为 y[x]
类型)。只要编译器允许自己在解析实际要写入的地址之前确定该类型,就没有什么可以阻止编译器执行该优化并创建未定义行为。 - Theodore Murdock不需要要求任何一侧在另一侧之前进行评估的原因有很多。更有趣的问题是,赋值运算符本身在执行任何操作之前是否需要计算双方的评估结果,包括副作用。我建议这种要求将会减轻某些别名限制,但在某些情况下需要编译器完成更多的工作。例如,假设“foo”和“bar”都是指向大型结构体的指针,其地址会重叠。语句“*foo = *bar;”在当前标准下代表未定义行为。如果在操作数评估和赋值之间存在一个序列点,则可以保证这种语句可以“正常工作”。这种保证将需要更复杂的赋值运算符,即使在实践中指针永远不会重叠,也需要更大更慢的代码。
例子:
unsigned char foo[100]; typedef struct {int x, int y;} POINT; POINT *p1 = (POINT*)foo; POINT *p2 = (POINT*)(&(p1->y));
根据上述声明,我认为以下语句具有严格定义的行为,并且不涉及任何未定义行为。
p1->y = somevalue; // 将 p2->x 设置为 somevalue p2->x = somevalue; // 将 p1->y 设置为 somevalue *p1 = mystruct; // 将 p2->x 设置为 mystruct.y *p2 = mystruct; // 将 p1->x 设置为 mystruct.x
然而,以下两个语句将涉及到未定义的行为: *p1 = *p2; *p2 = *p1;
如果等号处有一个序列点,编译器必须比较 p1 和 p2,或者将源操作数复制到临时位置,然后将其复制到目标位置。但是,标准明确指出上述两个语句都被视为未定义的行为。标准要求编译器在将一个结构体复制到非重叠的结构体时生成正确工作的代码,但对编译器在结构体重叠时可以做什么没有任何限制。如果编译器将处理器置于循环状态,向每个打开的 TCP 套接字发送“Frink Rules!”消息,这样做并不违反标准。
unsigned char [100]
对于任何需要对齐的内容都是正确对齐的吗?“你是在暗示结构体需要比其中任何元素更大的对齐方式吗?”也许是的。 - curiousguy这有点类似。operator=(可以由工程师定义(也称用户定义的类类型operator=))只是函数调用的语法糖。因此,它具有与函数调用相同的“序列点”语义。
如果我们谈论内置类型,那么我认为这是一件好事。
您不希望引入太多的序列点,因为这会妨碍优化。
operator=
的参数将是=
符号左右两侧的操作数,而函数调用不会对其参数的评估顺序施加限制——只有函数本身的评估是一个序列点。 - Karl Knechtel
ptr && ptr->data
这样的习惯用法。然而,&&之所以被标准要求具有短路行为,是因为标准规定如果左侧为假,则右侧不能被评估。因此,在左侧为假的情况下不允许先评估右侧。 :) - Karl Knechtel=
同样适用,只是方向相反,你需要评估右侧才能将其分配到右侧。但由于=
不是序列点,所以i++ = i++
是未定义的,而i++ && i++
则是已定义的。 - Šimon Tóth=
时,您必须评估左侧和右侧,然后进行赋值(只是对于每个侧面的“评估”有略微不同的规则-lvalues vs rvalues等)。但是,在实际分配之前,没有理由必须先评估左侧还是右侧-只要在实际分配之前两者都要评估即可。而使用&&
时,必须先评估左侧,然后再评估右侧,因为可能根本不需要评估右侧。 - Karl Knechtel