45

私は次のコードでそれを期待していました:

#include<stdio.h> 
int main(){
    int i = 10; 
    int j = 10;

    j = ++(i | i);
    printf("%d %d\n", j, i);

    j = ++(i & i);
    printf("%d %d\n", j, i);

    return 1;
}

j = ++(i | i);j = ++(i & i);は、次のように左辺値エラーを生成します。

x.c: In function ‘main’:
x.c:6: error: lvalue required as increment operand
x.c:9: error: lvalue required as increment operand   

しかし、以下のように、上記のコードが正常にコンパイルされたことに驚きました。

~$ gcc x.c -Wall
~$ ./a.out 
11 11
12 12   

上記のコードが正しく機能していることを確認してください。

他の演算子はエラーを生成しますが(私が理解しているように)。ビット演算子XORでさえエラーの原因になります(他の演算子がコンパイル時に左辺値エラーを生成することをj = ++(i ^ i);確認してください)。

理由は何ですか?これは未指定ですか、それとも未定義ですか?またはビット単位のORAND演算子は異なりますか?

コンパイラバージョン:

gcc version 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5)

しかし、コンパイラのバージョンが不均一な動作の理由になるべきではないと私は信じています。コンパイルされていない場合^は、コンパイルされ|&いません。そうでなければ、すべてのために働くはずです

c99モードのこのコンパイラではエラーではありません:gcc x.c -Wall -std=c99

4

5 に答える 5

28

コンパイルすべきではないというのは正しいです。ほとんどのコンパイラでは、コンパイルされません。
(どのコンパイラ/バージョンでコンパイラ エラーが発生していないかを正確に指定してください)

私は、コンパイラが と の ID を認識して(i | i) == iおり(i & i) == i、これらの ID を使用して式を最適化し、変数i.

これは単なる推測ですが、私にとっては非常に理にかなっています。

于 2013-02-13T18:26:01.827 に答える
25

これは、最近のGCCバージョンで対処されたバグです。

これはおそらく、コンパイラがに最適化さi & iiているi | iためiです。これは、xor演算子が機能しなかった理由も説明しています。i ^ iに最適化されますが0、これは変更可能な左辺値ではありません。

于 2013-02-13T18:28:55.320 に答える
17

C11 (n1570)、§ 6.5.3.1 プレフィックスのインクリメントおよびデクリメント演算子プレフィックスのインクリメントまたはデクリメント演算子のオペランドは、アトミック、修飾、または非修飾の実数またはポインター型を持ち、変更可能な lvalue
でなければなりません。

C11 (n1570)、§ 6.3.2.1
左辺値、配列、および関数指示子構造体または共用体には、const 修飾型のメンバー (含まれているすべての集合体または共用体のメンバーまたは要素を再帰的に含む) がありません。

C11 (n1570)、§ 6.3.2.1 左辺値、配列、および関数指示子左辺値は、潜在的にオブジェクトを指定する
( 以外のオブジェクト型を持つ) 式です。void

C11 (n1570)、§ 3. 用語、定義、および記号
オブジェクト: 実行環境におけるデータ ストレージの領域。その内容は値を表すことができます。

私の知る限り、潜在的には「存在することはできるが、まだ存在していない」ことを意味します。ただし(i | i)、実行環境でデータ ストレージのリージョンを参照することはできません。したがって、左辺値ではありません。これは古い gcc バージョンのバグのようで、その後修正されました。コンパイラを更新してください!

于 2013-02-13T18:33:42.410 に答える
7

私の質問へのフォローアップです。参考になるように、精巧な回答を追加しました。

私のコード式j = ++(i | i);j = ++(i & i);は、左辺値エラーの原因ではありませんか?

@abelenkyが答えたようにコンパイラの最適化の(i | i) == iため(i & i) == i. それはまさに正しいです。

私のコンパイラ(gcc version 4.4.5)では、単一の変数と結果を含む式は変更されません。単一の変数 ( not an expressionと呼ばれるもの) に最適化されます。

例えば:

j = i | i      ==> j = i
j = i & i      ==> j = i
j = i * 1      ==> j = i
j = i - i + i  ==> j = i 

==>意味optimized to

それを観察するために、私は小さな C コードを書き、それを で逆アセンブルしましたgcc -S

C コード: (コメントを読む)

#include<stdio.h>
int main(){
    int i = 10; 
    int j = 10;
    j = i | i;      //==> j = i
        printf("%d %d", j, i);
    j = i & i;      //==> j = i
        printf("%d %d", j, i);
    j = i * 1;      //==> j = i
    printf("%d %d", j, i);
    j = i - i + i;  //==> j = i
    printf("%d %d", j, i);
}

アセンブリ出力: (コメントを読む)

main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $10, 28(%esp)   // i 
    movl    $10, 24(%esp)   // j

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax  //j = i
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf  

上記のアセンブリ コードでは、すべての式が次のコードに変換されます。

movl    28(%esp), %eax  
movl    %eax, 24(%esp)

j = iこれはC コードと同等です。したがってj = ++(i | i);、 およびj = ++(i & i);は に最適化されj = ++iます。

注意: j = (i | i)は as 式(i | i) が C のステートメント (nop) ではないステートメントです。

したがって、私のコードは正常にコンパイルできました。

コンパイラで左辺値エラーが発生するのはなぜですかj = ++(i ^ i);?j = ++(i * i);j = ++(i | k);

いずれかの式に定数値があるか、変更できない左辺値 (最適化されていない式) があるためです。

asmコードを使用して観察できます

#include<stdio.h> 
int main(){
    int i = 10; 
    int j = 10;
    j = i ^ i;
    printf("%d %d\n", j, i);
    j = i - i;
    printf("%d %d\n", j, i);
    j =  i * i;
    printf("%d %d\n", j, i);
    j =  i + i;
    printf("%d %d\n", j, i);        
    return 1;
}

アセンブリ コード: (コメントを読む)

main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    movl    $10, 28(%esp)      // i
    movl    $10, 24(%esp)      // j

    movl    $0, 24(%esp)       // j = i ^ i;
                               // optimized expression i^i = 0
    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    $0, 24(%esp)      //j = i - i;
                              // optimized expression i - i = 0
    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax    //j =  i * i;
    imull   28(%esp), %eax
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    28(%esp), %eax   // j =  i + i;
    addl    %eax, %eax
    movl    %eax, 24(%esp)

    movl    $.LC0, %eax
    movl    28(%esp), %edx
    movl    %edx, 8(%esp)
    movl    24(%esp), %edx
    movl    %edx, 4(%esp)
    movl    %eax, (%esp)
    call    printf

    movl    $1, %eax
    leave

したがって、lvalue errorオペランドは変更可能な左辺値ではないため、これは を生成します。また、不均一な動作は、gcc-4.4 でのコンパイラの最適化によるものです。

新しい gcc コンパイラ (またはほとんどのコンパイラ) が左辺値エラーを生成するのはなぜですか?

式の評価とインクリメント(++)演算子の実際の定義を禁止する ++(i | i)ためです。++(i & i)

Dennis M. Ritchie の著書「The C Programming Language」のセクション「2.8 Increment and Decrement Operators」44 ページによると。

インクリメントおよびデクリメント演算子は、変数にのみ適用できます。(i+j)++ のような式は不正です。オペランドは、算術型またはポインター型の変更可能な左辺値でなければなりません。

ここで新しい gcc コンパイラ 4.47 でテストしたところ、予想どおりエラーが発生しました。tcc コンパイラでもテストしました。

これに関するフィードバック/コメントは素晴らしいでしょう。

于 2013-02-14T16:38:08.260 に答える
1

最適化エラーだとはまったく思いません。もしそうなら、そもそもエラーはないはずです。++(i | i)が に最適化されている場合、は左辺値++(i)であるため、エラーは発生しません。(i)

(i | i)私見、コンパイラーは式の出力と見なし、明らかに右辺値を出力すると思いますが、インクリメント演算子++は左辺値がそれを変更することを期待しているため、エラーが発生します。

于 2013-03-16T14:34:03.380 に答える