一行中的多个赋值

我刚刚看到嵌入式c语句(dsPIC33)

sample1 = sample2 = 0; 

这意味着什么

 sample1 = 0; sample2 = 0; 

他们为什么这样输入? 这是好还是坏编码?

请记住,赋值从右到左完成,并且它们是正常表达式。 所以从编译器的角度来看

 sample1 = sample2 = 0; 

是相同的

 sample1 = (sample2 = 0); 

这是一样的

 sample2 = 0; sample1 = sample2; 

也就是说, sample2被赋值为零,然后为sample1赋值sample2 。 在实践中,就像你猜到的那样将两者分配给零。

forms上,对于两个变量tu分别为TU

 T t; U u; 

分配

 t = u = X; 

(其中X是某个值)被解释为

 t = (u = X); 

并相当于一对独立的任务

 u = X; t = (U) X; 

注意, X的值应该达到变量t “好像”它首先通过变量u ,但是没有要求它实际上以这种方式发生。 在分配给t之前, X只需要转换为u类型。 该值不必先分配给u ,然后从u复制到t 。 上述两个任务实际上没有排序,可以按任何顺序发生,这意味着

 t = (U) X; u = X; 

也是此表达式的有效执行计划。 (请注意,这种排序自由特定于C语言,其中rvalue中的赋值结果。在C ++赋值中,求值为左值,这需要对“链式”赋值进行排序。)

没有更多的背景,没有办法说是一个好的或坏的编程实践。 如果两个变量紧密相关(如点的xy坐标),使用“链式”赋值将它们设置为某个常用值实际上是非常好的练习(我甚至会说“推荐练习”)。 但是当变量完全不相关时,将它们混合在一个“链式”赋值中绝对不是一个好主意。 特别是如果这些变量具有不同的类型,则可能导致意想不到的后果。

我认为如果没有实际的汇编列表,C语言就没有好的答案:)

所以对于一个简单的程序:

 int main() { int a, b, c, d; a = b = c = d = 0; return a; } 

我当然有这个assembly(Kubuntu,gcc 4.8.2,x86_64)和-O0选项;)

 main: pushq %rbp movq %rsp, %rbp movl $0, -16(%rbp) ; d = 0 movl -16(%rbp), %eax ; movl %eax, -12(%rbp) ; c = d movl -12(%rbp), %eax ; movl %eax, -8(%rbp) ; b = c movl -8(%rbp), %eax ; movl %eax, -4(%rbp) ; a = b movl -4(%rbp), %eax ; popq %rbp ret ; return %eax, ie. a 

所以gcc 实际上链接所有的东西。

您可以自己决定这种编码方式的好坏。

  1. 只需在IDE中查看以下行的汇编代码即可。

  2. 然后将代码更改为两个单独的分配,并查看差异。

除此之外,您还可以尝试在编译器中关闭/开启优化(大小和速度优化),以查看它如何影响汇编代码。

结果是一样的。 有些人喜欢链接分配,如果它们都是相同的值。 这种方法没有错。 就个人而言,如果变量具有密切相关的含义,我发现这更为可取。

 sample1 = sample2 = 0; 

的意思是

 sample1 = 0; sample2 = 0; 

当且仅当sample2声明了sample2
你不能这样做:

 int sample1 = sample2 = 0; //sample1 must be declared before assigning 0 to it 

关于编码风格和各种编码建议,请参见此处: 可读性a = b = c或a = c; B = C;?

我相信通过使用

 sample1 = sample2 = 0; 

与2个任务相比,一些编译器会产生稍快的程序集:

 sample1 = 0; sample2 = 0; 

特别是如果您要初始化为非零值。 因为,多重赋值转换为:

 sample2 = 0; sample1 = sample2; 

因此,您只需执行一次和一次复制,而不是2次初始化。 加速(如果有的话)将是微小的,但在嵌入式情况下,每一个微小的位数!

正如其他人所说,执行此命令的顺序是确定性的。 =运算符的运算符优先级保证从右到左执行。 换句话说,它保证sample2在sample1之前被赋予一个值。

但是 ,在一行上进行多次分配是不好的做法,并且被许多编码标准禁止(*)。 首先,它不是特别易读(或者你不会问这个问题)。 其次,这很危险。 如果我们有例如

 sample1 = func() + (sample2 = func()); 

然后运算符优先级保证与以前相同的执行顺序(+具有比=更高的优先级,因此括号)。 sample2将在sample1之前分配一个值。 但与运算符优先级不同,运算符的求值顺序不是确定性的,而是未指定的行为。 我们无法知道最左边的函数调用是在最左边的函数调用之前进行的。

编译器可以自由地将上面的内容翻译成机器代码,如下所示:

 int tmp1 = func(); int tmp2 = func(); sample2 = tmp2; sample1 = tmp1 + tmp2; 

如果代码依赖于func()按特定顺序执行,那么我们就创建了一个讨厌的bug。 它可以在程序的某个位置正常工作,但在同一程序的另一部分中断,即使代码相同。 因为编译器可以按照它喜欢的任何顺序自由地评估子表达式。


(*)MISRA-C:2004 12.2,MISRA-C:2012 13.4,CERT-C EXP10-C。