C 算术赋值运算符-左侧仅计算一次

C 算术赋值运算符-左侧仅计算一次,c,variable-assignment,C,Variable Assignment,正如标题所说,我在一些C课堂讲稿中发现了这样一个句子 我不能发明任何例子来证明那句话 在我看来,每个赋值操作都被评估一次,因为当我们希望它被评估不止一次时,我们就把它放入一个循环中。那么我错过了什么 我已经搜索过了,但在这里找不到答案。C说: (C99,6.5.16.2p3)“形式为E1 op=E2的复合赋值与简单赋值表达式E1=E1 op(E2)的不同之处在于左值E1只计算一次。” 以下是一些重要原因的示例: 示例1: a[i++] += 1; *foo() += 1; REG |= 0

正如标题所说,我在一些C课堂讲稿中发现了这样一个句子

我不能发明任何例子来证明那句话

在我看来,每个赋值操作都被评估一次,因为当我们希望它被评估不止一次时,我们就把它放入一个循环中。那么我错过了什么

我已经搜索过了,但在这里找不到答案。C说:

(C99,6.5.16.2p3)“形式为E1 op=E2的复合赋值与简单赋值表达式E1=E1 op(E2)的不同之处在于左值E1只计算一次。”

以下是一些重要原因的示例:

示例1:

 a[i++] += 1;
*foo() += 1;
REG |= 0x01;
同:

 a[i] = a[i] + 1; i++;
a[i++] = a[i++] + 1;
因为
+=
的左操作数只计算一次

如果不进行一次评估,则与以下内容相同:

 a[i] = a[i] + 1; i++;
a[i++] = a[i++] + 1;
这当然是不同的(顺便说一句,未定义的行为)

示例2:

 a[i++] += 1;
*foo() += 1;
REG |= 0x01;
这里假设
foo
返回指向标量类型对象的指针并产生副作用(例如,它在终端上打印字符串)。使用复合赋值运算符,它将只打印字符串一次,而不是两次

示例3:

 a[i++] += 1;
*foo() += 1;
REG |= 0x01;
假设
REG
这里有一个IO寄存器(类似于
#define REG(*(volatile uint8_t*)0x42)
),并且对该特定IO寄存器的每次读取都会触发一个硬件事件。使用复合赋值运算符只能读取寄存器一次,不能读取两次


编辑:下面的评论删除了示例3。我认为大多数编译器不会在这个表达式中执行一次读取:
REG=31
,或者用这个表达式执行两次读取:
REG=REG | 0x01

您的问题非常不清楚,措辞也很糟糕,但是我怀疑你的笔记所指的是算术+赋值运算符的组合允许你做某些事情,而不必多次为左值编写(并因此计算)表达式。比如说,

*p++ += *q++;  /* p is incremented once, as desired */
*p++ = *p++ + *q++;  /* undefined behavior */
在宏中使用这些命令时,这一点尤其重要,例如:

#define ACCUM(d,s) (d)+=(s) /* good */
#define ACCUM(d,s) (d)=(d)+(s) /* dangerous */
char* f() {
  static char value = 'a';
  printf("%c\n",value);
  return &value;
}

void g() {
  *f() = 'b'; // assigns 'b' which was 'a'
  *f() += 2; // changes 'b' to 'd'
  *f() = 'b';
  *f() = *f() + 2; // changes 'b' to 'd'
}

没有任何东西可以用handy编译,但这里有一个有趣的小贴士:

var1+=var++
会将var1的值更改为
var1+var

然而


var1+=++var
会将var1的值更改为
var1+(var+1)

在C中有一些复合赋值操作。例如+=,-=,*=,/=,%%=


例如,i++=1它将i的值增加1,类似于i++.

通常以以下方式引入
+=
运算符:

x += y;
x = x+y; // does the same
但是,注释试图告诉您,这实际上并不准确,因为
=
+=
的左侧可能是任何表达式。正如其他人所说,这可能导致未定义的行为,但这不是问题的核心

例如:

#define ACCUM(d,s) (d)+=(s) /* good */
#define ACCUM(d,s) (d)=(d)+(s) /* dangerous */
char* f() {
  static char value = 'a';
  printf("%c\n",value);
  return &value;
}

void g() {
  *f() = 'b'; // assigns 'b' which was 'a'
  *f() += 2; // changes 'b' to 'd'
  *f() = 'b';
  *f() = *f() + 2; // changes 'b' to 'd'
}

不同之处在于,
f
在最后一行执行两次,而在第二行执行一次。

有许多复合赋值运算符。例如,
+=,-=,*=,/=,%=

as
a+=b
给出a=a+b

有关C++的详细信息,请参阅

没有实际解释问题的赋值,而不是E.<代码> X+=6 < /COD> >哪一个,尽管它是C++的,但它解释了操作员的LHS对一次进行评估意味着什么。除了提到运算符重载(仅在C++中),注释适用于C和<代码> += (和<代码>·=< /代码>和其他赋值运算符)。问题是对作业的LHS进行一次评估意味着什么——为什么(或何时)如此重要?我已经熟悉你写的东西,而不是我在这里问的情况。无论如何,谢谢你的活动;)我认为这是问题中的理解(或不是问题);问题是对作业的LHS进行一次评估意味着什么-为什么(或何时)如此关键?哦,我的错误,我想我完全没有抓住问题的重点。示例3不正确。无论您是单独使用
|=
还是
|
=
,寄存器都将精确读取一次,精确写入一次。这是因为在计算
=
运算符时,不会读取左值引用的对象。也许您想的是,
=
可能会执行单个指令的读-修改-写循环,而
=
分别不会执行,但我认为这个假设是错误的;volatile不要求这样做,而且(在对volatile的要求的严格阅读下)甚至可能不允许这样做。@R.我可能对示例3太快了,因为它与真实世界的示例不符。实际上,大多数编译器可能不会发出带有简单赋值的读取,如
REG=31
(例如
vobj1
in)。但我认为编译器可以执行它。我看不出这句话与C标准的观点有什么不同:
REG也不使用该值,但大多数编译器执行读取。@R。顺便说一句,谢谢您的评论,我编辑了我的答案。
REGREG
不稳定,则code>应导致读取,因为表达式的值为
REG
,即使未使用。但是,读取
REG
不是计算表达式
REG=31的一部分
因为
=
运算符不计算其左侧的值;它只确定右边的值将被存储的位置。C表示赋值运算符的左操作数将被计算以确定其结果(C99,6.5.16p3)“赋值表达式在赋值后具有左操作数的值,但不是左值。”在C11中