Это ошибка в g++?
#include <stdint.h>
#include <iostream>
using namespace std;
uint32_t k[] = {0, 1, 17};
template <typename T>
bool f(T *data, int i) {
return data[0] < (T)(1 << k[i]);
}
int main() {
uint8_t v = 0;
cout << f(&v, 2) << endl;
cout << (0 < (uint8_t)(1 << 17)) << endl;
return 0;
}
g++ a.cpp && ./a.out
1
0
Почему я получаю эти результаты?
Ответы
Ответ 1
Похоже, gcc отменяет сдвиг и применяет его к другой стороне, и я думаю, что это ошибка.
В C (вместо С++) происходит то же самое, и C, переведенный в asm, легче читать, поэтому я использую C здесь; также я уменьшил тестовые примеры (отбрасывая шаблоны и массив k).
foo() является исходной функцией fug(), foo1() - это то, что foo() ведет себя как с gcc, но не должно, а bar() показывает, что должно выглядеть foo(), кроме прочитанного указателя.
Я нахожусь на 64-битном, но 32-разрядный - это то же самое, что и обработка параметров и поиск k.
#include <stdint.h>
#include <stdio.h>
uint32_t k = 17;
char foo(uint8_t *data) {
return *data < (uint8_t)(1<<k);
/*
with gcc -O3 -S: (gcc version 4.7.2 (Debian 4.7.2-5))
movzbl (%rdi), %eax
movl k(%rip), %ecx
shrb %cl, %al
testb %al, %al
sete %al
ret
*/
}
char foo1(uint8_t *data) {
return (((uint32_t)*data) >> k) < 1;
/*
movzbl (%rdi), %eax
movl k(%rip), %ecx
shrl %cl, %eax
testl %eax, %eax
sete %al
ret
*/
}
char bar(uint8_t data) {
return data < (uint8_t)(1<<k);
/*
movl k(%rip), %ecx
movl $1, %eax
sall %cl, %eax
cmpb %al, %dil
setb %al
ret
*/
}
int main() {
uint8_t v = 0;
printf("All should be 0: %i %i %i\n", foo(&v), foo1(&v), bar(v));
return 0;
}
Ответ 2
Если ваш int
имеет длину 16 бит, вы выполняете поведение undefined, и результат будет "ОК".
Смещение N-разрядных целых чисел на N или более позиций влево или вправо приводит к поведению undefined.
Так как это происходит с 32-битными int, это ошибка в компиляторе.
Ответ 3
Вот еще несколько точек данных:
в основном, он выглядит как gcc оптимизирует (даже когда флаг -O выключен и -g включен):
[variable] < (type-cast)(1 << [variable2])
к
((type-cast)[variable] >> [variable2]) == 0
и
[variable] >= (type-cast)(1 << [variable2])
к
((type-cast)[variable] >> [variable2]) != 0
где [variable] должен быть доступ к массиву.
Я предполагаю, что преимущество заключается в том, что ему не нужно загружать литерал 1 в регистр, который сохраняет 1 регистр.
Итак, вот точки данных:
- изменение 1 на число > 1 заставляет его реализовать правильную версию.
- изменение любой из переменных в литерал заставляет его реализовать правильную версию
- изменение [variable] в доступе без массива заставляет его реализовать правильную версию
- [variable] > (type-cast) (1 < < [переменная2]) реализует правильную версию.
Я подозреваю, что все это пытается сохранить регистр. Когда [variable] является доступом к массиву, он также должен содержать индекс. Кто-то, вероятно, думал, что это так умно, пока оно не ошибается.
Использование кода из отчета об ошибке http://gcc.gnu.org/bugzilla/show_bug.cgi?id=56051
#include <stdio.h>
int main(void)
{
int a, s = 8;
unsigned char data[1] = {0};
a = data[0] < (unsigned char) (1 << s);
printf("%d\n", a);
return 0;
}
скомпилированный с помощью gcc -O2 -S
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $8, %esp
pushl $1 ***** seems it already precomputed the result to be 1
pushl $.LC0
pushl $1
call __printf_chk
xorl %eax, %eax
movl -4(%ebp), %ecx
leave
leal -4(%ecx), %esp
ret
скомпилировать только gcc -S
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ebx
pushl %ecx
subl $16, %esp
movl $8, -12(%ebp)
movb $0, -17(%ebp)
movb -17(%ebp), %dl
movl -12(%ebp), %eax
movb %dl, %bl
movb %al, %cl
shrb %cl, %bl ****** (unsigned char)data[0] >> s => %bl
movb %bl, %al %bl => %al
testb %al, %al %al = 0?
sete %dl
movl $0, %eax
movb %dl, %al
movl %eax, -16(%ebp)
movl $.LC0, %eax
subl $8, %esp
pushl -16(%ebp)
pushl %eax
call printf
addl $16, %esp
movl $0, %eax
leal -8(%ebp), %esp
addl $0, %esp
popl %ecx
popl %ebx
popl %ebp
leal -4(%ecx), %esp
ret
Я предполагаю, что следующий шаг - выкачать исходный код gcc.
Ответ 4
Я уверен, что мы говорим о поведении undefined здесь - преобразование "большого" целого числа в меньшее, значения, которое не соответствует размеру нового значения, - это undefined как я знаю. 131072 определенно не вписывается в uint_8.
Хотя, глядя на код, сгенерированный, я бы сказал, что он, вероятно, не совсем прав, поскольку он делает "sete", а не "setb"??? Это кажется мне очень подозрительным.
Если я верну выражение:
return (T)(1<<k[i]) > data[0];
то он использует инструкцию "seta", чего я ожидаю. Я сделаю немного больше копания, но что-то кажется немного неправильным.