Выражения "j = ++ (i | i) и j = ++ (i & i); должна быть ошибкой lvalue?

Я ожидал, что в моем следующем коде:

#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); будут выдавать ошибки lvalue, как показано ниже:

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); (проверьте, что другие операторы производят ошибку lvalue во время компиляции).

В чем причина? Это неуказано или undefined? или побитовые OR AND, разные?

версия компилятора:

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

Но я считаю, что версия компилятора не должна служить причиной неравномерного поведения. Если ^ не скомпилирован, то | и & тоже нет. в противном случае это должно работать для всех

Это не ошибка с этим компилятором в режиме c99: gcc x.c -Wall -std=c99.

Ответ 1

Вы правы, что он не должен компилироваться, а на большинстве компиляторов он не компилируется.
(Пожалуйста, укажите, какой компилятор/версия НЕ дает вам ошибку компилятора)

Я могу только предположить, что компилятор знает тождества, что (i | i) == i и (i & i) == i, и использует эти тождества для оптимизации выражения, просто оставив переменную i.

Это просто догадка, но для меня это имеет большое значение.

Ответ 2

Это ошибка, которая была рассмотрена в более поздних версиях GCC.

Вероятно, потому, что компилятор оптимизирует i & i до i и i | i до i. Это также объясняет, почему оператор xor не работал; i ^ i будет оптимизирован до 0, который не является изменяемым значением l.

Ответ 3

C11 (n1570), § 6.5.3.1 Операторы приращения и уменьшения префиксов
Операнд оператора приращения или уменьшения префикса должен иметь атомный, или неквалифицированный реальный или указательный тип, и должен быть изменяемым значением l.

C11 (n1570), § 6.3.2.1 Lvalues, массивы и обозначения функций
Модифицируемое lvalue является значением l, которое  не имеет типа массива, не имеет неполного типа, не имеет const- квалифицированный тип, и если это структура или объединение, не имеет члена (включая,  рекурсивно, любой член или элемент всех содержащихся агрегатов или союзов) с const- квалифицированный тип.

C11 (n1570), § 6.3.2.1 Lvalues, массивы и обозначения функций
Lvalue - это выражение (с типом объекта, отличным от void), которое потенциально обозначает объект.

C11 (n1570), § 3. Термины, определения и символы
Объект: область хранения данных в среде исполнения, содержимое которой может представлять Значения

Насколько я знаю, потенциально означает "способный быть, но еще не существующим". Но (i | i) не может ссылаться на область хранения данных в среде исполнения. Поэтому это не значение. Это, похоже, ошибка в старой версии gcc, исправленной с тех пор. Обновите свой компилятор!

Ответ 4

Просто последую за моим вопросом. Я добавил подробный ответ, чтобы найти его полезным.

В моих кодовых выражениях j = ++(i | i); и j = ++(i & i); не вызваны для ошибки lvalue?

Из-за оптимизации компилятора, как @abelenky ответил (i | i) == i и (i & i) == i. Это точно ПРАВИЛЬНО.

В моем компиляторе (gcc version 4.4.5) любое выражение, которое включает одну переменную и результат, не изменяется; оптимизированный в одну переменную (что называется не выражением).

например:

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-Code: (читать комментарии)

#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) - это выражение, в выражении (i | i) не утверждение (nop) в C

Следовательно, мой код может быть успешно скомпилирован.

Почему j = ++(i ^ i); или j = ++(i * i);, j = ++(i | k); производят ошибку lvalue в моем компиляторе?

Потому что либо выражение имеет постоянное значение, либо не изменяемое значение lvalue (неоптимизированное выражение).

, мы можем наблюдать с помощью asm code

#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, потому что операнд не является изменяемым значением l. И неравномерное поведение связано с оптимизацией компилятора в gcc-4.4.

Почему новые gcc-компиляторы (или большинство компиляторов) создают ошибку lvalue?

Поскольку оценка выражения ++(i | i) и ++(i & i) запрещает фактическое деинфискацию оператора increment (++).

Согласно книге Денниса М. Ричи " Язык программирования C в разделе " 2.8 Операторы приращения и уменьшения "стр. 44.

Операторы приращения и уменьшения могут применяться только к переменным; выражение, подобное (i + j) ++, является незаконным. Операнд должен быть модифицируемым lvalue арифметики или типа указателя.

Я тестировал новый gcc компилятор 4.47 здесь, он вызывает ошибку, как я ожидал. Я также тестировался на tcc-компиляторе.

Любая обратная связь/комментарии по этому вопросу были бы замечательными.

Ответ 5

Я вообще не думаю, что это ошибка оптимизации, потому что если бы это было так, то в первую очередь не должно было быть ошибок. Если ++(i | i) оптимизирован для ++(i), то не должно быть никакой ошибки, потому что (i) является lvalue.

IMHO, я думаю, что компилятор видит (i | i) как вывод выражения, который, очевидно, выдает значение rvalue, но оператор приращения ++ ожидает, что lvalue изменит его, таким образом, ошибку.