20
#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

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

4

4 回答 4

20

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

在 C(而不是 C++)中也会发生同样的事情,并且将 C 翻译成 asm 更容易阅读,所以我在这里使用 C;我还减少了测试用例(删除模板和 k 数组)。foo() 是原始的错误 f() 函数, foo1() 是 foo() 在 gcc 中的行为,但不应该, bar() 显示 foo() 除了读取的指针之外应该是什么样子。

我是 64 位的,但除了参数处理和查找 k 之外,32 位是相同的。

#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;
}
于 2013-01-20T11:20:12.333 回答
8

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

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

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

于 2013-01-20T10:23:33.507 回答
4

以下是更多数据点:

基本上,它看起来像 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] 是数组访问时,它还需要保留一个索引。有人可能认为这很聪明,直到它错了。

使用错误报告中的代码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 的源代码。

于 2013-01-20T14:24:21.667 回答
-1

I'm pretty sure we're talking undefined behaviour here - converting a "large" integer to a smaller, of a value that doesn't fit in the size of the new value, is undefined as far as I know. 131072 definitely doesn't fit in a uint_8.

Although looking at the code generated, I'd say that it's probably not quite right, since it does "sete" rather than "setb"??? That does seem very suspicios to me.

If I turn the expression around:

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

then it uses a "seta" instruction, which is what I'd expect. I'll do a bit more digging - but something seems a bit wrong.

于 2013-01-20T12:48:42.030 回答