我刚看到嵌入式c(dsPIC33)中的语句
sample1 = sample2 = 0;
这是否意味着
sample1 = 0;
sample2 = 0;
为什么他们这样输入?这是好还是坏编码?
答案 0 :(得分:37)
请记住,赋值是从右到左完成的,并且它们是正常表达式。所以从编译器的角度看这行
sample1 = sample2 = 0;
与
相同sample1 = (sample2 = 0);
与
相同sample2 = 0;
sample1 = sample2;
即,sample2
被指定为零,然后sample1
被指定为sample2
的值。在实践中,就像你猜到的那样将两者分配给零。
答案 1 :(得分:8)
正式地,分别为t
和u
类型的两个变量T
和U
T t;
U u;
作业
t = u = X;
(其中X
是某个值)被解释为
t = (u = X);
并且相当于一对独立的作业
u = X;
t = (U) X;
请注意,X
的值应该是变量t
“,就好像”它首先通过变量u
一样,但是没有要求它实际上以这种方式发生。 X
只需转换为u
类型,然后再分配到t
。该值不必先分配到u
,然后从u
复制到t
。上述两个作业实际上没有排序,可以按任何顺序发生,这意味着
t = (U) X;
u = X;
也是此表达式的有效执行计划。 (请注意,这种排序自由特定于C语言,其中rvalue中的赋值结果。在C ++赋值中,求值为左值,这需要对“链式”赋值进行排序。)
如果没有更多的背景,没有办法说这是一个好的或坏的编程实践。如果两个变量紧密相关(如点的x
和y
坐标),使用“链式”赋值将它们设置为某个常用值实际上是非常好的做法(我甚至说“推荐做法”)。但是当变量完全不相关时,将它们混合在一个“链式”赋值中绝对不是一个好主意。特别是如果这些变量具有不同的类型,可能会导致意想不到的后果。
答案 2 :(得分:6)
我认为如果没有实际的汇编列表,对C语言没有好的答案:)
所以对于一个简单的程序:
int main() {
int a, b, c, d;
a = b = c = d = 0;
return a;
}
我有这个装配(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 实际上链接所有的东西。
答案 3 :(得分:1)
结果是一样的。有些人喜欢链接分配,如果它们都是相同的值。这种方法没有错。就个人而言,如果变量具有密切相关的含义,我觉得这更可取。
答案 4 :(得分:1)
你可以自己决定这种编码方式的好坏。
只需在IDE中查看以下行的汇编代码即可。
然后将代码更改为两个单独的分配,并查看差异。
除此之外,您还可以尝试在编译器中关闭/开启优化(尺寸和速度优化)以查看它对汇编代码的影响。
答案 5 :(得分:0)
sample1 = sample2 = 0;
意味着
sample1 = 0;
sample2 = 0;
当且仅当先前声明sample2
时
你不能这样做:
int sample1 = sample2 = 0; //sample1 must be declared before assigning 0 to it
答案 6 :(得分:0)
关于编码风格和各种编码建议,请参见此处: Readability a=b=c or a=c; b=c;?
通过使用
我相信sample1 = sample2 = 0;
与2个任务相比,某些编译器会产生稍快的程序集:
sample1 = 0;
sample2 = 0;
特别是如果您要初始化为非零值。因为,多重赋值转换为:
sample2 = 0;
sample1 = sample2;
因此,您只需执行一次和一次复制,而不是2次初始化。加速(如果有的话)将是微小的,但在嵌入式情况下,每一个微小的位数都很重要!
答案 7 :(得分:0)
正如其他人所说,执行此命令的顺序是确定性的。 =运算符的 运算符优先级 保证从右到左执行此操作。换句话说,它保证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。
答案 8 :(得分:0)
照顾这个特殊情况... 假设b是以下形式的结构的数组
{
int foo;
}
,让我成为b中的偏移量。考虑函数realloc_b()返回一个int并执行数组b的重新分配。考虑这种多重分配:
a =(b + i)-> foo = realloc_b();
以我的经验(b + i)首先得到解决,让我们说它是RAM中的b_i;然后执行realloc_b()。当realloc_b()更改RAM中的b时,将导致不再分配b_i。
变量a分配合理,但(b + i)-> foo并非因为b已被更改 通过执行赋值最左边的项,即realloc_b()
这可能导致分段错误,因为b_i可能位于未分配的RAM位置。
要实现无缺陷且(b + i)-> foo等于a,必须将单行分配分为两个分配:
a = reallocB();
(b + i)->foo = a;
答案 9 :(得分:0)
如前所述,
sample1 = sample2 = 0;
等于
sample2 = 0;
sample1 = sample2;
问题是 riscy问有关嵌入式c 的问题,嵌入式c通常用于直接驱动寄存器。许多微控制器的寄存器在读写操作上有不同的用途。因此,在一般情况下,与
不同sample2 = 0;
sample1 = 0;
例如,让UDR作为UART数据寄存器。从UDR读取意味着从输入缓冲区中获取接收到的值,而写入UDR意味着将所需值放入发送缓冲区中并触发通信。在这种情况下,
sample = UDR = 0;
表示以下内容:a)使用UART(UDR = 0;
)传输零值,b)读取输入缓冲区并将数据放入sample
值(sample = UDR;
)中。
您可能会看到,嵌入式系统的行为可能比代码编写者预期的复杂得多。在对MCU进行编程时,请小心使用此表示法。