我试图在没有模板的情况下进行交换,但遇到了这个问题。 a+=k
和a=a+b
是否不同?第一种情况怎么了?
a += b-(b=a); // this print same value of two a and b.
a = a + b-(b=a); // this thing correctly swapped values.
答案 0 :(得分:4)
这两种行为都会导致不确定的行为,因为您都在访问和修改b
时没有插入中间点:
如果相对于任何一个标量对象的副作用未排序 对相同标量对象或值的不同副作用 使用相同标量对象的值进行计算,其行为是 未定义。如果存在多个允许的排序 表达式的子表达式,如果这样的行为是不确定的 在任何顺序中都会发生无序的副作用。
以下是使用+/-的方法(仅对无符号数字类型使用UB-free,因为有符号+/-可能会导致未定义的溢出):
a+=b, b=a-b, a=a-b;
或使用xor(始终不带UB):
a^=b, b^=a, a^=b;
Gcc和clang似乎可以识别这两种模式,并通过临时方式将它们编译为交换,这是常见体系结构上最有效的方法:https://gcc.godbolt.org/z/3W_22r
答案 1 :(得分:2)
这两个语句都会导致未定义的行为,因此不应期望它们给出相同的结果。
根据C11草案标准§6.5p2
如果相对于相同标量对象上的不同副作用或使用相同标量对象的值进行的值计算,相对于标量对象的副作用未排序,则行为未定义。
在已发布的代码中,(b=a)
是一个表达式,其计算结果为a
,但是具有分配a
值的副作用到b
。在这两个语句中,都从(b=a)
中减去了a
的值(即b
的值),但是这里的b
是值计算得出b
的值。这两个表达式之间没有sequence point,也就是说,值计算b
是在(b=a)
之前还是之后是不确定的。由于在这两个语句中对b
的副作用和b
的值计算都是未排序的,因此都导致未定义的行为。
最好避免这样的“聪明”代码。在这种情况下,看似聪明的代码具有不确定的行为。在其他情况下,聪明的代码可能对于那些必须维护代码的人甚至以后的人来说很难理解。编写清晰易懂且易于维护的代码。仅在遇到性能问题并找出问题的根源时才担心优化。编译器很聪明,可以识别常见的惯用法,并且在打开优化功能时能够为这些常见的惯用法生成接近最佳的代码。很少需要逗号运算符或不直观的XOR交换;对于简单的交换(这是编程中的常见操作),请使用显而易见的解决方案,然后让编译器完成工作。
显而易见的解决方案使用与a
和b
相同类型的临时变量,易于理解,永远不会有未定义的行为(甚至与实现相关的行为),并且经常由优秀的编译器进行优化,使其代码的性能要比善意的程序员的微观优化所产生的代码更好:
temp = a;
a = b;
b = temp;
答案 2 :(得分:0)
来自C11 standard 6.5.16.2.3
形式为
E1 op= E2
的复合赋值与简单赋值表达式E1 = E1 op (E2)
等效,除了左值E1仅被评估一次,并且对于不确定顺序的函数调用而言,该运算复合分配的结果是单个评估。