【问题标题】:Any good reason why assignment operator isn't a sequence point?赋值运算符不是序列点的任何充分理由?
【发布时间】:2011-05-20 17:39:23
【问题描述】:

operator = 不是序列点有什么好的理由吗?在 C 和 C++ 中。

我很难想出一个反例。

【问题讨论】:

  • 这将需要编译器在 RHS 之前计算 LHS。为什么要创建这种限制?
  • 一般来说,事情需要一个理由来成为一个序列点。他们不需要理由成为序列点;这是默认设置。
  • && 是一个序列点,因为它允许像 ptr && ptr->data 这样的成语工作。相反,因为标准要求&& 具有短路行为:标准规定如果左侧评估为假,则右侧根本不能评估 .因此,不允许先评估右侧,以防左侧为假。 :)
  • 看。使用=,您必须同时评估左侧和右侧,然后进行分配(只是“评估”每一侧的规则有些不同 - 左值与右值等)。但是没有理由你必须先评估左侧,然后再评估右侧,反之亦然——只要你在实际分配之前两者都做。对于&&,您必须评估左侧右侧,因为可能根本必须评估右侧.
  • @Karl 哦,对。好的,很酷,你能把它写成答案吗?

标签: c++ c sequence-points


【解决方案1】:

根据要求:

一般来说,事情需要一个理由来成为一个序列点。他们不需要理由成为序列点;这是默认设置。

例如,&& 由于短路行为必须是一个序列点:如果左侧为假,则右侧不得评估。 (这不仅仅是关于优化;右侧可能有副作用,和/或取决于左侧是否为真,如ptr && ptr->data。)因此必须先评估左侧,然后右侧,以便查看是否应该评估右侧。

= 不存在这个原因,因为尽管双方都需要进行“评估”(尽管对双方可以出现的内容有不同的限制:左侧必须是左值 - l 不代表“左”,顺便说一句;它代表“位置”,就像在内存中的位置 - 我们不能分配给临时或文字),先评估哪一方并不重要- 只要双方在实际分配之前都经过评估。

【讨论】:

  • +1,但= 不是序列点确实咬了我一次:我有myArray[i++] = <expression containing i>;,两个编译器之一在 LHS 之后计算了 RHS,产生了错误的答案。最后是我的错,但仍然很棘手。
  • IIRC 和 FWIW,Java 在这里提供了更多保证 :)
  • 短路行为本身不需要序列点。像(((ch = *x++) != 0) && (*y++ = ch)) 这样的表达式可以,但是对于 && 强加一个序列点的明确要求,延迟 x 的增量直到表达式的第二部分执行之后,因为编译器可以确定之前无论&& 的左侧操作数是否会计算为零,都会递增 x。在实践中,序列点禁止的任何代码重排都不太可能有用,但在某些理论上的情况下可能有用。
  • 怎么样:x = y[x] = 0?是否保证 y[x] 将在 x 设置为 0 之前评估(使用 x 的旧值)?
  • @Ariel 我认为标准没有说明任何可以保证排序的内容,因为这里没有序列点。编译器在确定要写入的地址之前确定赋值表达式 y[x] = 0 的值也很简单,因为该值只是右手边(或其隐式转换为 y[x] 的类型) .如果编译器允许自己确定该类型而不首先解析要写入的实际地址,则没有什么能阻止编译器执行该优化并创建未定义的行为。
【解决方案2】:

它是(有点)。 operator=(可以由工程师定义(也就是用户定义的类类型的 operator=))只是函数调用的语法糖。因此,它具有与函数调用相同的“序列点”语义。

如果我们采用内置类型,那么我认为这是一件好事。
您不想引入太多序列点,因为这会妨碍优化。

【讨论】:

  • 它只是 C++ 中用户定义类的函数调用的语法糖。虽然您有点暗示,但从您的措辞方式来看根本不清楚。
  • '"sequence point" semantics' 并不是特别有用,因为 operator= 的参数将是 = 符号的 LHS 和 RHS,而函数调用不会对其参数的评估施加顺序 - 只是函数本身的评估是一个序列点。
【解决方案3】:

有很多理由不要求任何一方在另一方之前进行评估。一个更有趣的问题是,在赋值运算符本身做任何事情之前,是否需要对双方进行评估,包括副作用。我建议这样的要求会减轻一些别名限制,但在某些情况下需要编译器做更多的工作。例如,假设“foo”和“bar”是指向地址会重叠的大型结构的指针。语句“*foo = *bar;”将代表当前标准下的未定义行为。如果在操作数的计算和赋值之间有一个序列点,那么这样的语句将保证“工作”。这样的保证对于赋值运算符来说需要更复杂,需要更大更慢的代码,即使在实践中指针永远不会重叠。

例子:

无符号字符 foo[100]; typedef struct {int x, int y;} POINT; 点 *p1 = (点*)foo; 点 *p2 = (点*)(&(p1->y));

鉴于上述声明,我认为以下陈述具有严格定义的行为,不涉及任何未定义的行为。

p1->y = 某个值; // 将 p2->x 设置为某个值 p2->x = 某个值; // 将 p1->y 设置为某个值 *p1 = mystruct; // 将 p2->x 设置为 mystruct.y *p2 = mystruct; // 将 p1->x 设置为 mystruct.x

但是,以下两个语句将涉及未定义行为:

*p1 = *p2; *p2 = *p1;

如果等号处有序列点,编译器必须比较 p1 和 p2,或者将源操作数复制到临时位置,然后将其复制到目标位置。然而,该标准清楚地表明,上述两个语句都被认为是未定义的行为。该标准要求编译器生成在将结构复制到非重叠结构时可以正常工作的代码,但对结构重叠时编译器可以执行的操作没有任何限制。一个编译器,它将处理器进入循环发送“Frink 规则!”这样做不会违反标准。

【讨论】:

  • 地址会重叠的大型结构。”你能举个例子吗?
  • 你从“辛普森一家”的荷马 3D 剧集中得到了“弗林克规则”这句话,不是吗?
  • @curiousguy:什么样的对齐问题?您是否建议结构需要比其中任何元素更大的对齐方式?
  • "什么样的对齐问题?" 你认为unsigned char [100] 是否正确对齐...任何需要对齐的东西? “你是说结构需要比其中任何元素都大的对齐方式吗?”也许。
  • @curiousguy:公平点。假设 foo 是通过 calloc() 获得的 char*。那会有什么问题吗?
猜你喜欢
  • 2015-06-01
  • 2012-04-02
  • 2013-04-18
  • 2018-11-13
  • 2011-08-02
  • 2019-10-06
  • 2011-11-16
  • 2015-02-25
  • 1970-01-01
相关资源
最近更新 更多