33

我刚刚在嵌入式 c (dsPIC33) 中遇到了语句

sample1 = sample2 = 0;

这是否意味着

sample1 = 0;

sample2 = 0;

他们为什么这样打字?这是好还是坏的编码?

4

10 回答 10

55

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

sample1 = sample2 = 0;

是相同的

sample1 = (sample2 = 0);

这与

sample2 = 0;
sample1 = sample2;

即,sample2赋值为零,然后sample1赋值为sample2。在实践中,这与您猜测的将两者都分配为零相同。

于 2013-10-14T04:53:08.470 回答
11

形式上,对于两个变量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,然后从复制ut。上面的两个赋值实际上是没有顺序的,可以按任何顺序发生,这意味着

t = (U) X;
u = X;

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

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

于 2013-10-14T08:16:56.337 回答
9

我认为没有实际汇编列表的 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实际上是在链接所有的东西。

于 2014-07-08T20:18:23.667 回答
1

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

于 2013-10-14T04:54:21.790 回答
1

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

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

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

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

于 2013-10-14T05:31:53.290 回答
1

如前所述,

sample1 = sample2 = 0;

等于

sample2 = 0;
sample1 = sample2;

问题是riscy询问了嵌入式 c,它通常用于直接驱动寄存器。许多微控制器的寄存器在读写操作上都有不同的用途。所以,在一般情况下,它是不一样的,因为

sample2 = 0;
sample1 = 0;

例如,让 UDR 为 UART 数据寄存器。从 UDR 读取意味着从输入缓冲区获取接收到的值,而写入 UDR 意味着将所需值放入传输缓冲区并进行通信。在这种情况下,

sample = UDR = 0;

含义如下:a) 使用 UART ( ) 传输零值UDR = 0;和 b) 读取输入缓冲区并将数据放入samplevalue ( sample = UDR;)。

您可以看到,嵌入式系统的行为可能比代码编写者预期的要复杂得多。在对 MCU 进行编程时,请谨慎使用此符号。

于 2019-11-16T18:53:18.197 回答
0
sample1 = sample2 = 0;

确实意味着

sample1 = 0;
sample2 = 0;  

当且仅当sample2是较早声明的。
你不能这样做:

int sample1 = sample2 = 0; //sample1 must be declared before assigning 0 to it
于 2013-10-14T04:52:15.557 回答
0

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

我相信通过使用

sample1 = sample2 = 0;

与 2 个分配相比,某些编译器生成程序集的速度会稍快一些:

sample1 = 0;
sample2 = 0;

特别是如果您正在初始化为非零值。因为,多重分配转化为:

sample2 = 0; 
sample1 = sample2;

因此,您只需进行一份和一份副本,而不是 2 次初始化。加速(如果有的话)将很小,但在嵌入式情况下,每一点都很重要!

于 2013-10-14T05:18:14.827 回答
0

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

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

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

then 运算符优先级保证与之前相同的执行顺序(+ 的优先级高于 =,因此是括号)。sample2 将在 sample1 之前获得一个值。但与运算符优先级不同,运算符的评估顺序不是确定性的,它是未指定的行为。我们不知道最右边的函数调用在最左边的函数调用之前被评估。

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

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

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


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

于 2013-10-14T06:31:45.807 回答
0

照顾这种特殊情况......假设 b 是形式结构的数组

{
    int foo;
}

让 i 成为 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;
于 2018-12-07T14:09:22.517 回答