简体   繁体   English

一行多任务

[英]Multiple assignment in one line

I just come across the statement in embedded c (dsPIC33)我刚刚遇到了嵌入式 c (dsPIC33) 中的语句

sample1 = sample2 = 0;

Would this mean这是否意味着

sample1 = 0;

sample2 = 0;

Why do they type it this way?他们为什么这样打字? Is this good or bad coding?这是好还是坏的编码?

Remember that assignment is done right to left, and that they are normal expressions.请记住,赋值是从右到左完成的,它们是普通表达式。 So from the compilers perspective the line所以从编译器的角度来看,这条线

sample1 = sample2 = 0;

is the same as是一样的

sample1 = (sample2 = 0);

which is the same as这与

sample2 = 0;
sample1 = sample2;

That is, sample2 is assigned zero, then sample1 is assigned the value of sample2 .也就是说,将sample2分配为零,然后将sample1分配给sample2的值。 In practice the same as assigning both to zero as you guessed.实际上,与您猜测的将两者都分配为零相同。

Formally, for two variables t and u of type T and U respectively形式上,对于分别属于TU类型的两个变量tu

T t;
U u;

the assignment任务

t = u = X;

(where X is some value) is interpreted as (其中X是某个值)被解释为

t = (u = X);

and is equivalent to a pair of independent assignments并且等价于一对独立的赋值

u = X;
t = (U) X;

Note that the value of X is supposed to reach variable t "as if" it has passed through variable u first, but there's no requirement for it to literally happen that way.请注意, X的值应该“就好像”它首先通过变量u到达变量t一样,但并不要求它确实以这种方式发生。 X simply has to get converted to type of u before being assigned to t . X只需在分配给t之前转换为u类型。 The value does not have to be assigned to u first and then copied from u to t .该值不必先分配给u然后从u复制到t The above two assignments are actually not sequenced and can happen in any order, meaning that上述两个赋值实际上没有顺序,可以以任何顺序发生,这意味着

t = (U) X;
u = X;

is also a valid execution schedule for this expression.也是此表达式的有效执行计划。 (Note that this sequencing freedom is specific to C language, in which the result of an assignment in an rvalue. In C++ assignment evaluates to an lvalue, which requires "chained" assignments to be sequenced.) (请注意,这种排序自由是特定于 C 语言的,其中右值中的赋值结果。在 C++ 中,赋值计算为左值,这需要对“链式”赋值进行排序。)

There's no way to say whether it is a good or bad programming practice without seeing more context.如果不了解更多上下文,就无法判断这是一种好的还是坏的编程实践。 In cases when the two variables are tightly related (like x and y coordinate of a point), setting them to some common value using "chained" assignment is actually perfectly good practice (I'd even say "recommended practice").在这两个变量紧密相关的情况下(例如一个点的xy坐标),使用“链式”赋值将它们设置为某个公共值实际上是非常好的做法(我什至会说“推荐做法”)。 But when the variables are completely unrelated, then mixing them in a single "chained" assignment is definitely not a good idea.但是当变量完全不相关时,将它们混合在一个“链式”赋值中绝对不是一个好主意。 Especially if these variables have different types, which can lead to unintended consequences.特别是如果这些变量具有不同的类型,这可能会导致意想不到的后果。

I think there is no good answer on C language without actual assembly listing :)我认为没有实际的汇编列表就没有关于 C 语言的好答案:)

So for a simplistic program:所以对于一个简单的程序:

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

I've got this assemly (Kubuntu, gcc 4.8.2, x86_64) with -O0 option of course ;)我当然有这个带有-O0选项的组件(Kubuntu,gcc 4.8.2,x86_64);)

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

So gcc is actually chaining all the stuff.所以 gcc实际上是链接所有的东西。

The results are the same.结果是一样的。 Some people prefer chaining assignments if they are all to the same value.如果它们都具有相同的值,有些人更喜欢链接分配。 There is nothing wrong with this approach.这种方法没有任何问题。 Personally, I find this preferable if the variables have closely related meanings.就我个人而言,如果变量具有密切相关的含义,我认为这更可取。

You can yourself decide that this way of coding is good or bad.您可以自己决定这种编码方式是好是坏。

  1. Simply see the assembly code for the following lines in your IDE.只需在 IDE 中查看以下行的汇编代码。

  2. Then change the code to two separate assignments, and see the differences.然后将代码更改为两个单独的分配,并查看差异。

In addition to this, you can also try turning off/on optimizations (both Size & Speed Optimizations) in your compiler to see how that affects the assembly code.除此之外,您还可以尝试在编译器中关闭/打开优化(大小和速度优化)以查看它如何影响汇编代码。

As noticed earlier,如前所述,

sample1 = sample2 = 0;

is equal to等于

sample2 = 0;
sample1 = sample2;

The problem is that riscy asked about embedded c , which is often used to drive registers directly.问题是riscy询问了嵌入式 c ,它通常用于直接驱动寄存器。 Many of microcontroller's registers have a different purpose on read and write operations.许多微控制器的寄存器在读写操作上有不同的用途。 So, in gereral case, it is not the same , as所以,在一般情况下,它是不一样的,因为

sample2 = 0;
sample1 = 0;

For example, let UDR be a UART data register.例如,让 UDR 是一个 UART 数据寄存器。 Reading from UDR means getting the recieved value from the input buffer, while writing to UDR means putting the desired value into transmit buffer and hitting the communication.从 UDR 读取意味着从输入缓冲区获取接收到的值,而写入 UDR 意味着将所需的值放入传输缓冲区并进行通信。 In that case,在那种情况下,

sample = UDR = 0;

means the following: a) transmit value of zero using UART ( UDR = 0; ) and b) read input buffer and place data into sample value ( sample = UDR; ).表示以下内容:a) 使用 UART 传输零值 ( UDR = 0; ) 和 b) 读取输入缓冲区并将数据放入sample值 ( sample = UDR; )。

You could see, the behavior of embedded system could be much more complicated than the code writer may expect to be.您可以看到,嵌入式系统的行为可能比代码编写者预期的要复杂得多。 Use this notation carefully while programming MCUs.在对 MCU 进行编程时请谨慎使用此符号。

sample1 = sample2 = 0;

does mean确实意味着

sample1 = 0;
sample2 = 0;  

if and only if sample2 is declared earlier.当且仅当sample2声明了sample2
You can't do this way:你不能这样做:

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

Regarding coding style and various coding recommendations see here: Readability a=b=c or a=c;关于编码风格和各种编码建议,请参见此处: 可读性 a=b=c 或 a=c; b=c;? b=c;?

I beleive that by using我相信通过使用

sample1 = sample2 = 0;

some compilers will produce an assembly slightly faster in comparison to 2 assignments:与 2 个赋值相比,某些编译器生成程序集的速度会稍快一些:

sample1 = 0;
sample2 = 0;

specially if you are initializing to a non-zero value.特别是如果您要初始化为非零值。 Because, the multiple assignment translates to:因为,多重赋值转化为:

sample2 = 0; 
sample1 = sample2;

So instead of 2 initializations you do only one and one copy.因此,您只进行一次和一次复制而不是 2 次初始化。 The speed up (if any) will be tiny but in embedded case every tiny bit counts!加速(如果有的话)会很小,但在嵌入式情况下,每一点都很重要!

As others have said, the order in which this gets executed is deterministic.正如其他人所说,执行此操作的顺序是确定性的。 The operator precedence of the = operator guarantees that this is executed right-to-left. = 运算符的运算符优先级保证从右到左执行。 In other words, it guarantees that sample2 is given a value before sample1.换句话说,它保证在 sample1 之前给 sample2 一个值。

However , multiple assignments on one row is bad practice and banned by many coding standards (*).但是,在一行上进行多项分配是不好的做法,并且被许多编码标准 (*) 禁止。 First of all, it is not particularly readable (or you wouldn't be asking this question).首先,它的可读性不是特别好(否则你就不会问这个问题了)。 Second, it is dangerous.其次,它是危险的。 If we have for example如果我们有例如

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

then operator precedence guarantees the same order of execution as before (+ has higher precedence than =, therefore the parenthesis).然后运算符优先级保证与以前相同的执行顺序(+ 的优先级高于 =,因此是括号)。 sample2 will get assigned a value before sample1. sample2 将在 sample1 之前分配一个值。 But unlike operator precedence, the order of evaluation of operators is not deterministic, it is unspecified behavior.但与运算符优先级不同,运算符的求值顺序不是确定性的,它是未指定的行为。 We can't know that the right-most function call is evaluated before the left-most one.我们无法知道最右边的函数调用在最左边的函数调用之前被评估。

The compiler is free to translate the above to machine code like this:编译器可以自由地将上述内容转换为机器代码,如下所示:

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

If the code depends on func() getting executed in a particular order, then we have created a nasty bug.如果代码依赖于 func() 以特定顺序执行,那么我们就创建了一个令人讨厌的错误。 It may work fine in one place of the program, but break in another part of the same program, even though the code is identical.它可能在程序的一个地方工作得很好,但在同一程序的另一部分中断,即使代码是相同的。 Because the compiler is free to evaluate sub-expressions in any order it likes.因为编译器可以按照它喜欢的任何顺序自由地评估子表达式。


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

Take care of this special case ... suppose b is an array of a structure of the form注意这个特殊情况……假设 b 是一个结构数组

{
    int foo;
}

and let i be an offset in b.让我成为 b 中的一个偏移量。 Consider the function realloc_b() returning an int and performing the reallocation of array b.考虑函数 realloc_b() 返回一个 int 并执行数组 b 的重新分配。 Consider this multiple assignment:考虑这个多重分配:

a = (b + i)->foo = realloc_b(); a = (b + i)->foo = realloc_b();

To my experience (b + i) is solved first, let us say it is b_i in the RAM ;根据我的经验 (b + i) 首先解决,让我们说它是 RAM 中的 b_i ; then realloc_b() is executed.然后 realloc_b() 被执行。 As realloc_b() changes b in RAM, it results that b_i is no more allocated.由于 realloc_b() 更改了 RAM 中的 b,导致不再分配 b_i。

Variable a is well assigned but (b + i)->foo is not because b as been changed bythe execution of the most left term of the assignment ie realloc_b()变量 a 被很好地赋值,但 (b + i)->foo 不是因为 b 被赋值的最左边一项即 realloc_b() 的执行改变了

This may cause a segmentation fault since b_i is potentially in an unallocated RAM location.这可能会导致分段错误,因为 b_i 可能位于未分配的 RAM 位置。

To be bug free, and to have (b + i)->foo equal to a, the one-line assignment must be splitted in two assignments:为了避免错误,并使 (b + i)->foo 等于 a,一行赋值必须拆分为两个赋值:

a = reallocB();
(b + i)->foo = a;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM