web-dev-qa-db-ja.com

1行に複数の割り当て

埋め込まれたc(dsPIC33)のステートメントに出会ったばかりです。

sample1 = sample2 = 0;

これはどういう意味ですか

sample1 = 0;

sample2 = 0;

なぜこのように入力するのですか?これは良いコーディングですか、それとも悪いコーディングですか?

24
riscy

割り当ては右から左に行われ、通常の式であることを忘れないでください。コンパイラの観点からすると、

sample1 = sample2 = 0;

と同じです

sample1 = (sample2 = 0);

と同じです

sample2 = 0;
sample1 = sample2;

つまり、sample2にはゼロが割り当てられ、sample1にはsample2の値が割り当てられます。実際には、推測したとおりに両方をゼロに割り当てるのと同じです。

38

正式には、タイプtおよびuの2つの変数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にコピーする必要はありません。上記の2つの割り当ては実際には順序付けられておらず、任意の順序で発生する可能性があります。

t = (U) X;
u = X;

この式の有効な実行スケジュールでもあります。 (このシーケンスの自由度は、右辺値での割り当ての結果であるC言語に固有であることに注意してください。C++割り当てでは左辺値に評価され、「連鎖」割り当てをシーケンスする必要があります。)

より多くのコンテキストを見ずに、それが良いプログラミング手法であるか悪いプログラミング手法であるかを言う方法はありません。 2つの変数が密接に関連している場合(ポイントのxおよびy座標など)、「連鎖」割り当てを使用して一般的な値に設定することは、実際には完全に良い習慣です(私は「推奨される練習」とさえ言います)。しかし、変数が完全に無関係である場合、それらを単一の「連鎖」割り当てに混在させることは間違いなく良い考えではありません。特に、これらの変数の型が異なる場合、意図しない結果が生じる可能性があります。

8
AnT

私は実際のアセンブリのリストなしで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は実際すべてを連鎖します。

6
hurufu

結果は同じです。一部の人々は、すべてが同じ値である場合、割り当てを連鎖することを好みます。このアプローチには何の問題もありません。個人的には、変数が密接に関連した意味を持っている場合、これが望ましいと思います。

1
kjelderg

このコーディング方法が良いか悪いかを自分で決めることができます。

  1. IDEの次の行のアセンブリコードを参照してください。

  2. 次に、コードを2つの個別の割り当てに変更し、違いを確認します。

これに加えて、コンパイラーで最適化(サイズと速度の両方の最適化)をオフ/オンにして、それがアセンブリコードに与える影響を確認することもできます。

コーディングスタイルとさまざまなコーディングの推奨事項については、以下を参照してください。 可読性a = b = cまたはa = c; b = c;?

私はそれを信じて

sample1 = sample2 = 0;

一部のコンパイラは、2つの割り当てに比べてアセンブリをわずかに速く生成します。

sample1 = 0;
sample2 = 0;

特に、ゼロ以外の値に初期化する場合。なぜなら、複数の割り当ては次のように変換されるからです。

sample2 = 0; 
sample1 = sample2;

したがって、2回の初期化の代わりに、1つだけのコピーを実行します。 (もしあれば)スピードアップはわずかですが、組み込みの場合、すべての小さなビットがカウントされます!

0
P. B. M.

この特別な場合に注意してください... bが次の形式の構造体の配列であると仮定します

{
    int foo;
}

そして、bのオフセットとします。関数realloc_b()がintを返し、配列bの再割り当てを実行することを検討してください。この複数の割り当てを検討してください。

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

私の経験では、(b + i)が最初に解決され、RAM;でb_iであるとしましょう。その後、realloc_b()が実行されます。 b_iは割り当てられなくなりました。

変数aは適切に割り当てられますが、(b + i)-> fooは、割り当ての最も左の項の実行によってbが変更されたため、つまり、realloc_b()ではありません。

B_iは未割り当てのRAMロケーションにある可能性があるため、これによりセグメンテーションフォールトが発生する可能性があります。

バグをなくし、(b + i)-> fooをaと等しくするには、1行の割り当てを2つの割り当てに分割する必要があります。

a = reallocB();
(b + i)->foo = a;
0
Nicolas Rapin
sample1 = sample2 = 0;

意味する

sample1 = 0;
sample2 = 0;  

場合にのみsample2は以前に宣言されています。
この方法は使用できません。

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

他の人が言ったように、これが実行される順序は決定的です。 =演算子のoperator precedenceは、これが右から左に実行されることを保証します。つまり、sample1の前にsample2に値が与えられることを保証します。

ただし、、1行に複数の割り当てを行うのは悪い習慣であり、多くのコーディング標準(*)によって禁止されています。まず第一に、それは特に読みやすいものではありません(または、この質問をすることはないでしょう)。第二に、それは危険です。たとえば

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

演算子の優先順位は、以前と同じ実行順序を保証します(+は=よりも優先順位が高いため、括弧が付けられます)。 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.

0
Lundin