使用复合赋值的优点

时间:2018-04-19 13:17:25

标签: c c11 compound-assignment

在C / C ++中使用复合赋值的真正优势是什么(或者也可能适用于许多其他编程语言)?

#include <stdio.h>

int main()
{
    int exp1=20;
    int b=10;
   // exp1=exp1+b;
    exp1+=b;

    return 0;
};

我查看了一些链接,例如microsoft siteSO post1SO Post2。 但是优点是exp1仅在复合语句的情况下被评估一次。 exp1如何在第一种情况下真正评估两次?我知道首先读取exp1的当前值,然后添加新值。更新后的值将写回同一位置。在复合语句的情况下,如何在较低级别发生这种情况?我试图比较两种情况的汇编代码,但我没有看到它们之间有任何区别。

6 个答案:

答案 0 :(得分:3)

对于涉及普通变量的简单表达式,

之间的区别
a = a + b;

a += b;

仅限语法。这两个表达式的行为完全相同,可能会生成相同的汇编代码。 (你是对的;在这种情况下,询问a是否被评估一次或两次甚至没有意义。)

当有趣的地方是赋值的左侧是涉及副作用的表达式。所以如果你有像

这样的东西
*p++ = *p++ + 1;

*p++ += 1;

它带来了更大的不同!前者尝试两次递增p(因此未定义)。但后者恰好评估p++一次,并且定义明确。

正如其他人所提到的,还有标记便利性和可读性的优点。如果你有

variable1->field2[variable1->field3] = variable1->field2[variable2->field3] + 2;

很难发现错误。但是如果你使用

variable1->field2[variable1->field3] += 2;

甚至不可能有这个错误,后来的读者也不必仔细审查这些条款以排除这种可能性。

一个小优点是,它可以为您节省一对括号(或者如果您将这些括号留下,则可以保存错误)。考虑:

x *= i + 1;         /* straightforward */
x = x * (i + 1);    /* longwinded */
x = x * i + 1;      /* buggy */

最后(感谢Jens Gustedt提醒我这一点),我们必须回过头来仔细思考一下我们说的时候我们的意思&#34;它变得有趣的地方是左边的赋值是一种涉及副作用的表达。&#34;通常,我们认为修改是副作用,而访问是&#34; free&#34;。但是对于限定为volatile的变量(或者,在C11中,作为_Atomic),访问也算作一个有趣的副作用。因此,如果变量a具有其中一个限定符,a = a + b不是一个涉及普通变量&#34;的简单表达式,并且它可能与a += b不完全相同所有

答案 1 :(得分:2)

如果它不仅仅是一个简单的变量名,那么评估左侧一次可以为您节省很多。例如:

int x[5] = { 1, 2, 3, 4, 5 };
x[some_long_running_function()] += 5;

在这种情况下,some_long_running_function()仅被调用一次。这不同于:

x[some_long_running_function()] = x[some_long_running_function()] + 5;

两次调用该函数。

答案 2 :(得分:2)

这就是标准6.5.16.2所说的:

  

形式 E1 op = E2 的复合赋值相当于简单赋值表达式 E1 = E1 操作 E2 ),但左值 E1 仅评估一次

所以“评估一次”就是差异。这在嵌入式系统中非常重要,在这些系统中,您有volatile个限定符,并且不想多次读取硬件寄存器,因为这可能会导致不必要的副作用。

这不可能在SO上重现,所以这里有一个人为的例子来说明为什么多个评估会导致不同的程序行为:

#include <string.h>
#include <stdio.h>

typedef enum { SIMPLE, COMPOUND } assignment_t;

int index;

int get_index (void)
{
  return index++;
}

void assignment (int arr[3], assignment_t type)
{
  if(type == COMPOUND)
  {
    arr[get_index()] += 1;
  }
  else
  {
    arr[get_index()] = arr[get_index()] + 1;
  }
}

int main (void)
{
  int arr[3];

  for(int i=0; i<3; i++) // init to 0 1 2
  {
    arr[i] = i;
  }
  index = 0;
  assignment(arr, COMPOUND);
  printf("%d %d %d\n", arr[0], arr[1], arr[2]);   // 1 1 2

  for(int i=0; i<3; i++) // init to 0 1 2
  {
    arr[i] = i;
  }
  index = 0;
  assignment(arr, SIMPLE);
  printf("%d %d %d\n", arr[0], arr[1], arr[2]);   // 2 1 2 or 0 1 2
}

简单赋值版本不仅给出了不同的结果,还在代码中引入了未指定的行为,因此根据编译器可能会产生两种不同的结果。

答案 3 :(得分:1)

不确定你之后的情况。化合物分配比使用常规操作更短,因此更简单(更简单)。

考虑一下:

player->geometry.origin.position.x += dt * player->speed;

player->geometry.origin.position.x = player->geometry.origin.position.x + dt * player->speed;

哪一个更容易阅读和理解,并验证?

对我来说,这是一个非常非常真正的优势,无论语义细节如何被评估多少次都是如此。

答案 4 :(得分:1)

  

使用复合赋值的优点

也存在劣势 考虑类型的影响。

long long exp1 = 20;
int b=INT_MAX;

// All additions use `long long` math
exp1 = exp1 + 10 + b;
下面的

10 + b添加将使用int数学和溢出(未定义的行为

exp1 += 10 + b;  // UB 
// That is like the below,
exp1 = (10 + b) + exp1;

答案 5 :(得分:0)

像C这样的语言总是会成为底层机器操作码的抽象。在添加的情况下,编译器首先将左操作数移动到累加器中,并向其添加右操作数。像这样的东西(伪汇编代码):

move 1,a
add 2,a

这是1+2在汇编程序中编译的内容。显然,这可能过于简化了,但你明白了。

此外,编译器倾向于优化您的代码,因此exp1=exp1+b很可能编译为与exp1+=b相同的操作码。

而且,正如@unwind所说,复合语句更具可读性。