以下链式赋值是不是会导致未定义的行为?

Posted

技术标签:

【中文标题】以下链式赋值是不是会导致未定义的行为?【英文标题】:Does the following chained assignment cause Undefined behavior?以下链式赋值是否会导致未定义的行为? 【发布时间】:2015-07-30 01:32:39 【问题描述】:

以下代码是否会调用 C 中的未定义行为?

int a = 1, b = 2;
a = b = (a + 1);

我知道以下确实调用了 UB:

a = b = a++;

原因是它违反了标准中的以下条款:

在前一个序列点和下一个序列点之间,一个对象应该有它的 表达式的评估最多修改一次存储的值。 此外,应仅访问先验值以确定 要存储的值。

但是,第一个sn-p 并不违反此条款。一位同事说声明a = b = a+1 可能意味着任一

a = a + 1;
b = a + 1;

b = a + 1;
a = b;

我认为由于= 的“从右到左”的关联性,它总是意味着a = (b = (a+1)),而不是

a = a + 1;
b = a + 1;

不过,我并不积极。是UB吗?

【问题讨论】:

您对关联定律是正确的。表达式b = (a+1) 的结果是b 在赋值之后将取的值(尽管赋值在评估之前没有排序,无论如何在C11 之前,但这在这里无关紧要)。 赋值表达式的值是左操作数的值(赋值后)。所以,你的同事错了——它只能指第二个。 该副本与问题无关。但是,这可能是重复的:***.com/questions/19353686/… @gopi 恕我直言,这不是重复的。显然,OP知道a = a++是UB,他想知道a = b = (a + 1);是否也是UB。 请注意,a = a = a +1 的定义并不明确。因为在同一个表达式中对同一个变量有两个副作用,中间没有序列点。当然,如果你根本不使用这种称为“多重赋值”的危险、完全愚蠢和完全多余的功能,就不会有任何问题,也不会造成任何混乱。 【参考方案1】:

恕我直言,a = b = a+1 定义明确。

这里。你没有改变a的值,只是使用它,以防a+1

明确地说,根据= 的"right-to-left" associativity 运算符,您可以将上述分解为,

b = a + 1;  //a does not change here, only the value is used.
a = b;

【讨论】:

聪明的方法当然是像上面那样编写代码,而不是在一行上使用多个赋值。但是如果所有的 C 程序员(包括 Dennis Ritchie)都能够使用常识,我们就不会有这些关于运算符优先级和未定义行为的有趣讨论,那么 SO 上的流量就会大大减少! :)【参考方案2】:

这是 C99 的完整序列点列表(改编自 C99,附件 C):

在函数调用期间,在计算参数之后和函数体的任何部分执行之前; 在以下运算符的第一个操作数的末尾:&&||?:,; 在完整声明符的末尾; 在完整表达式(初始化程序、表达式语句中的表达式、return 语句中的表达式、for 语句中的每个表达式或if、@ 987654327@、whiledo声明); 就在库函数返回之前; 在与每个格式化输入/输出函数转换说明符关联的操作之后; 在每次调用比较函数之前和之后;以及对比较函数的任何调用与作为参数传递给该调用的对象的任何移动之间(参考bsearch()qsort()

从这个角度考虑您的代码:

int a = 1, b = 2;
a = b = (a + 1);

有序列点

    a = 1 之后(完整的声明符) b = 2 之后(完整声明符) 在第二个语句的末尾。 (完整表达)

整个a = b = (a + 1)是一个单一的表达式语句,它不包含内部序列点。但是,它并不违反禁止对象在序列点之间多次修改其值:ab 在整个语句中都只修改一次。

【讨论】:

也许与问题更相关,6.5.16(赋值运算符):The side effect of updating the stored value of the left operand is sequenced after the value computations of the left and right operands. The evaluations of the operands are unsequenced. 意思是我们不知道b 是否在a+1 之前被评估(没关系,除非变量是可变的)。 @Lundin,你一定在看 C11,而我的答案是在 C99 上下文中明确给出的。对应的 C99 文本是“更新左操作数的存储值的副作用应发生在前一个和下一个序列点之间。操作数的评估顺序未指定。”【参考方案3】:

a = b = a + 1; 等同于:

b = a + 1;
a = b;

你不会在右手边改变左手边的值,所以它是很好定义的。

【讨论】:

如果b 是volatile,则语句不等价;后一种表述要求编译器在编写之后读取b;前者可能会也可能不会,这取决于编译器[一些编译器保证赋值的值是类型强制存储的值,而不是变量在后面保存的值;针对此类编译器的代码可以避免对其他不必要的临时的需要。【参考方案4】:
a = a++;

相比不同
a = a+1;

在第二种情况下,您不会通过执行a+1 来修改a 的值,而在第一种情况下,a 的值正在更改,这将导致未定义的行为。

【讨论】:

以上是关于以下链式赋值是不是会导致未定义的行为?的主要内容,如果未能解决你的问题,请参考以下文章

在哪些情况下 std::optional operator == 会导致未定义的行为?

动态数组的惯用初始化是不是会调用未定义的行为?

别名可变原始指针 (*mut T) 会导致未定义的行为吗?

确保dynamic_cast不会导致未定义的行为C ++

OpenFoam:更改 STL 位置会导致未定义行为或崩溃

C++ 类型索引散列导致未定义的行为