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