C 使用复合赋值的优点

C 使用复合赋值的优点,c,c11,compound-assignment,C,C11,Compound Assignment,在C/C++中使用复合赋值的真正优势是什么(或者可能也适用于许多其他编程语言) #包括 int main() { int exp1=20; int b=10; //exp1=exp1+b; exp1+=b; 返回0; }; 我查看了一些链接,如。 但优点是,对于复合语句,exp1只计算一次。在第一种情况下,如何对exp1进行两次真正的评估?我知道先读取exp1的当前值,然后添加新值。更新后的值将写回同一位置。在复合语句的情况下,这在较低级别上是如何发生的?我试图比较两种情况下的汇编代码,但我没

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

#包括
int main()
{
int exp1=20;
int b=10;
//exp1=exp1+b;
exp1+=b;
返回0;
};
我查看了一些链接,如。
但优点是,对于复合语句,exp1只计算一次。在第一种情况下,如何对exp1进行两次真正的评估?我知道先读取exp1的当前值,然后添加新值。更新后的值将写回同一位置。在复合语句的情况下,这在较低级别上是如何发生的?我试图比较两种情况下的汇编代码,但我没有看到它们之间的任何差异。

不确定您要的是什么。复合赋值较短,因此比使用常规操作更简单(不太复杂)

考虑这一点:

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

player->geometry.origin.position.x = player->geometry.origin.position.x + dt * player->speed;
哪一个更容易阅读、理解和验证


对我来说,这是一个非常非常实际的优势,并且无论语义细节如何,比如某个东西被评估了多少次,这都是正确的。

像C这样的语言总是将底层机器操作码抽象出来。在加法的情况下,编译器首先将左操作数移动到累加器中,然后将右操作数添加到累加器中。类似这样的代码(伪汇编代码):

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

而且,编译器倾向于优化代码,因此
exp1=exp1+b
很可能编译成与
exp1+=b
相同的操作码


正如@unwind所说,复合语句的可读性要高得多。

对于包含普通变量的简单表达式

a = a + b;

只是句法上的。这两个表达式的行为完全相同,很可能生成相同的汇编代码。(你是对的;在这种情况下,询问
a
是否计算一次或两次都没有多大意义。)

有趣的是,作业的左侧是一个包含副作用的表达式。所以如果你有

*p++ = *p++ + 1;

*p++ += 1;
这会带来更大的不同!前者尝试两次递增
p
(因此未定义)。但是后者只计算
p++
一次,并且定义良好

正如其他人所提到的,也有方便和可读性的优点。如果你有

variable1->field2[variable1->field3] = variable1->field2[variable2->field3] + 2;
很难发现错误。但是如果你使用

variable1->field2[variable1->field3] += 2;
甚至不可能有这样的bug,以后的读者也不必仔细阅读这些术语来排除这种可能性

一个次要的优点是,它可以为您节省一对括号(如果您不使用这些括号,则可以避免出现错误)。考虑:

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

最后(感谢Jens Gustedt提醒我这一点),我们必须回头再仔细思考一下我们所说的“有趣之处在于作业的左侧是一个包含副作用的表达式”的意思。通常,我们认为修改是副作用,访问是“免费”的. 但是对于限定为
volatile
(或者,在C11中,限定为
\u Atomic
)的变量,访问也算作一个有趣的副作用。因此,如果变量
a
有一个限定符,
a=a+b
不是一个“包含普通变量的简单表达式”,毕竟它可能与
a+=b
并不完全相同。

如果不仅仅是一个简单的变量名,那么对左侧进行一次求值可以为您节省很多。例如:

int x[5] = { 1, 2, 3, 4, 5 };
x[some_long_running_function()] += 5;
在这种情况下,
一些长时间运行的函数()
只被调用一次。这不同于:

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

它调用函数两次。

这是标准6.5.16.2所说的:

形式为E1op=E2的复合赋值等价于简单赋值表达式E1=E1op(E2),除了左值E1仅计算一次之外

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

这在这里是不可能重现的,所以这里有一个人工示例来演示为什么多次评估可能导致不同的程序行为:

#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
}
#包括
#包括
typedef枚举{简单,复合}赋值;
整数指数;
int get_索引(void)
{
返回索引++;
}
无效分配(int arr[3],分配类型)
{
如果(类型==复合)
{
arr[get_index()]+=1;
}
其他的
{
arr[get_index()]=arr[get_index()]+1;
}
}
内部主(空)
{
int-arr[3];
对于(int i=0;i
使用复合赋值的优点

也有一个缺点。
考虑类型的影响。

long long exp1 = 20;
int b=INT_MAX;

// All additions use `long long` math
exp1 = exp1 + 10 + b;
10+b
下面的加法将使用
int
数学和溢出(未定义的行为)


您的编译器很有可能对其进行了优化。无论如何,汇编语言通常都有增量操作码。事实上,它们没有任何其他功能。如果您在C中执行类似于
1+2
的操作,它将被编译为类似
move 1、a
add 2、a
@severity的操作,那么答案应该是一个
你的例子的一个问题是你没有遵循德米特定律。
x+=dt*speed
x=x+dt*speed
之间的区别更小,但仍然值得拥有。我同意这个答案。现代编译器通常不需要任何“帮助”程序员使用特定的语言结构。事实上,今天代码片段更好的可读性通常也意味着编译器更好的优化。@unwind I a
x[some_long_running_function()] = x[some_long_running_function()] + 5;
#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
}
long long exp1 = 20;
int b=INT_MAX;

// All additions use `long long` math
exp1 = exp1 + 10 + b;
exp1 += 10 + b;  // UB 
// That is like the below,
exp1 = (10 + b) + exp1;