Выражения "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, чтобы изменить его, таким образом, ошибка.