一行中的多个赋值

时间:2013-10-14 04:50:38

标签: c embedded

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

sample1 = sample2 = 0;

这是否意味着

sample1 = 0;

sample2 = 0;

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

10 个答案:

答案 0 :(得分:37)

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

sample1 = sample2 = 0;

相同
sample1 = (sample2 = 0);

相同
sample2 = 0;
sample1 = sample2;

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

答案 1 :(得分:8)

正式地,分别为tu类型的两个变量TU

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 ++赋值中,求值为左值,这需要对“链式”赋值进行排序。)

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

答案 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)

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

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

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

  3. 除此之外,您还可以尝试在编译器中关闭/开启优化(尺寸和速度优化)以查看它对汇编代码的影响。

答案 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进行编程时,请小心使用此表示法。