一行中的多个赋值
我刚刚看到嵌入式c语句(dsPIC33)
sample1 = sample2 = 0;
这意味着什么
sample1 = 0; sample2 = 0;
他们为什么这样输入? 这是好还是坏编码?
请记住,赋值从右到左完成,并且它们是正常表达式。 所以从编译器的角度来看
sample1 = sample2 = 0;
是相同的
sample1 = (sample2 = 0);
这是一样的
sample2 = 0; sample1 = sample2;
也就是说, sample2
被赋值为零,然后为sample1
赋值sample2
。 在实践中,就像你猜到的那样将两者分配给零。
forms上,对于两个变量t
和u
分别为T
和U
型
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 ++赋值中,求值为左值,这需要对“链式”赋值进行排序。)
没有更多的背景,没有办法说是一个好的或坏的编程实践。 如果两个变量紧密相关(如点的x
和y
坐标),使用“链式”赋值将它们设置为某个常用值实际上是非常好的练习(我甚至会说“推荐练习”)。 但是当变量完全不相关时,将它们混合在一个“链式”赋值中绝对不是一个好主意。 特别是如果这些变量具有不同的类型,则可能导致意想不到的后果。
我认为如果没有实际的汇编列表,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 实际上是在链接所有的东西。
您可以自己决定这种编码方式的好坏。
-
只需在IDE中查看以下行的汇编代码即可。
-
然后将代码更改为两个单独的分配,并查看差异。
除此之外,您还可以尝试在编译器中关闭/开启优化(大小和速度优化),以查看它如何影响汇编代码。
结果是一样的。 有些人喜欢链接分配,如果它们都是相同的值。 这种方法没有错。 就个人而言,如果变量具有密切相关的含义,我发现这更为可取。
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。