Выражения "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 во время компиляции).

Какова причина? Это не указано или не определено? или побитовые операторы ИЛИ И разные?

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

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

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

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

5 ответов

Решение

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

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

Это всего лишь предположение, но оно имеет для меня большой смысл.

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

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

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

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

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

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

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

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

В моем коде выражения 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-код: (читать комментарии)

#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) не утверждение (нет) в C

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

Зачем j = ++(i ^ i); или же j = ++(i * i); , j = ++(i | k); выдать ошибку lvalue на моем компиляторе?

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

мы можем наблюдать, используя 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 потому что операнд не является изменяемым lvalue. А неравномерное поведение связано с оптимизацией компилятора в gcc-4.4.

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

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

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

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

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

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

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

ИМХО, я думаю, что компилятор видит (i | i) в качестве выражения output, которое, очевидно, выводит rvalue, но оператор приращения ++ ожидает lvalue, чтобы изменить его, таким образом, ошибка.

Другие вопросы по тегам