这是g++中的一个bug吗?

Is it a bug in g++?

本文关键字:一个 bug g++ 这是      更新时间:2023-10-16
#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"指令,这是我所期望的。我会再做一点调查,但似乎有些不对劲。