这是g++中的一个bug吗?
Is it a bug in 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
为什么我得到这些结果?
看起来gcc颠倒了shift并将其应用到另一边,我猜这是一个bug。
在C(而不是c++)中发生了同样的事情,C翻译成asm更容易阅读,所以我在这里使用C;我还减少了测试用例(删除模板和k数组)。Foo()是最初的有bug的f()函数,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 %in", foo(&v), foo1(&v), bar(v));
return 0;
}
如果你的int
是16位长,你会遇到未定义的行为,任何结果都是"OK"。
将N位整数向左或向右移动N位或更多位将导致未定义行为。
这里有更多的数据点:
基本上,它看起来像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 <<[variable2])实现正确的版本。
我怀疑这一切都是为了保存寄存器。当[variable]是数组访问时,它还需要保持索引。有人可能认为这很聪明,直到它错了。
使用bug报告中的代码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("%dn", 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
我很确定我们在这里谈论的是未定义的行为-将"大"整数转换为较小的值,其值不适合新值的大小,据我所知是未定义的。131072绝对不适合uint_8。
虽然查看生成的代码,我想说它可能不太正确,因为它是"设置"而不是"设置"??这在我看来确实很可疑。
如果我把表达式转过来:
return (T)(1<<k[i]) > data[0];
则使用"seta"指令,这是我所期望的。我会再做一点调查,但似乎有些不对劲。
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 我正在用 c++ 制作一个小时钟,但遇到了"bug"或某种问题
- 这个std::vector和std::shared_ptr内存泄漏是一个bug吗
- c++ 指向另一个类 bug "no operator matches these operands."的指针
- 奇怪的bug-子例程只运行一个cout
- 我用STL写了一个bin_search,但这是一个小BUG
- 在一个简单的策略模式的bug
- for循环中的奇怪行为-一个bug
- 可变模板元编程:clang++或g++中的一个bug
- 这是GCC中的一个bug吗?
- MSVC 2013 Bug?正在从映射的容器中检索最后一个元素
- 我最近在使用abs()时遇到了一个奇怪的bug
- libc++ std::search_n中的崩溃是一个bug吗?
- 在Qt中重现一个bug:调试代码时出现分段错误
- 这是g++中的一个bug吗?
- visual studio -这(崩溃)是VS2012 c++编译器中的一个bug吗?
- 一个非常简单的加法程序(c++)上的奇怪bug
- 这是Mac OS X 10.6 c++ std API中的一个bug吗?
- 调用mysql_close获取堆栈损坏,这是MySQL中的一个bug吗?