【问题标题】:Is it a bug in g++? [closed]它是g ++中的错误吗? [关闭]
【发布时间】:2013-01-20 22:32:12
【问题描述】:
#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

为什么我会得到这些结果?

【问题讨论】:

  • 这不是g++的bug,只是你不知道这应该怎么做。
  • 问题是有效的..为什么那些downvotes
  • @Some1.Kill.The.DJ:因为这表明提问者缺乏努力。至少,他本可以(应该)陈述他期望的结果,以及他期望这些结果的原因。
  • @ZdeslavVojkovic 如果1 &lt;&lt; 17 不会导致UB 并且uint8_t 是它所说的,它应该是0。
  • 有人在这里提交了 PR:gcc.gnu.org/bugzilla/show_bug.cgi?id=56051

标签: c++ gcc


【解决方案1】:

看起来 gcc 反转了转变并将其应用于另一侧,我猜这是一个错误。

在 C(而不是 C++)中也会发生同样的事情,并且将 C 翻译成 asm 更容易阅读,所以我在这里使用 C;我还减少了测试用例(删除模板和 k 数组)。 foo() 是原始的错误 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 %i\n", foo(&v), foo1(&v), bar(v));
    return 0;
}

【讨论】:

  • 终于有一句格言“过早的优化是万恶之源”真的适用!
  • 如果你想要比 asm 更容易阅读的东西,试试 gcc 中的 -fdump-tree-all 选项。
【解决方案2】:

如果您的 int 长度为 16 位,则您将遇到未定义的行为,并且任一结果均为“OK”。

将 N 位整数向左或向右移动 N 位或更多位会导致未定义的行为。

由于这发生在 32 位整数上,这是编译器中的一个错误。

【讨论】:

  • 这种情况发生在 GCC 的 32 位整数平台上。
  • @interjay 然后,除非我遗漏了什么,否则它看起来像一个错误。
  • 我认为 gcc 中有一个错误,并且这篇文章没有回答 OP。
  • @ouah 编辑了答案。
【解决方案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

我怀疑这一切都是为了保存一个寄存器。当 [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 的源代码。

【讨论】:

  • 如果您错过了它,PR 会为您提供 gcc 源代码中发生此优化的确切行...如果 lhs 是无符号的并且 rhs 是(a演员表)1
  • @MarcGlisse,它是如此具体,以至于我不禁想知道是否有人认为这是一个聪明的优化,可以在最需要的时候保存寄存器。否则,为什么非数组访问不会发生?什么是公关?我正在查看错误报告,但没有看到它。我尝试用局部变量 d 替换 data[0],这实际上使它起作用。还尝试将变量更改为文字,这也有效。
  • 非数组确实会发生这种情况,请不要使用这种粗鲁的 cmets。
  • 这段代码工作正常(d 没有得到优化):#include int main(void) { int a, s =8;诠释 d = 0; a = d
  • 也改变 int d = 0;为无符号字符 d = 0;也有效。
【解决方案4】:

我很确定我们在这里谈论的是未定义的行为——据我所知,将“大”整数转换为不适合新值大小的较小值是未定义的。 131072 绝对不适合 uint_8。

虽然查看生成的代码,但我想说它可能不太正确,因为它是“sete”而不是“setb”???这对我来说似乎很可疑。

如果我把表达式转过来:

return (T)(1<<k[i])  >  data[0];

然后它使用“seta”指令,这是我所期望的。我会做更多的挖掘 - 但似乎有点不对劲。

【讨论】:

  • 根据stackoverflow.com/questions/6752567/… 转换为无符号的较小类型是明确定义的。 (并且应该做我们期望的)
  • 我注意到您的代码也显示“sete”。我没有深入研究它,但对我来说似乎不合适。
  • 生成的代码相当于test eax, eax; setz al,这看起来是返回x == 0的合理方式,对于unsigned int,它与x &lt; 1相同。我觉得没问题。
  • 是的,但是 x
  • x > k) “相同”
猜你喜欢
  • 2013-04-14
  • 1970-01-01
  • 1970-01-01
  • 2016-06-22
  • 1970-01-01
  • 2023-03-29
  • 2015-03-04
  • 2012-12-04
  • 1970-01-01
相关资源
最近更新 更多