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でも同じことが起こり、asmに変換されたCは読みやすいので、ここでは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

16 ビット長の場合int、未定義の動作が発生しており、どちらの結果も「OK」です。

N ビット整数を N 個以上のビット位置だけ左または右にシフトすると、未定義の動作が発生します。

これは 32 ビット int で発生するため、コンパイラのバグです。

于 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

ここで、[変数]は配列アクセスである必要があります。

ここでの利点は、リテラル1をレジスターにロードする必要がないことです。これにより、1つのレジスターが節約されます。

したがって、ここにデータポイントがあります。

  • 1を1より大きい数に変更すると、正しいバージョンが実装されます。
  • 変数のいずれかをリテラルに変更すると、正しいバージョンが実装されます。
  • [変数]を非配列アクセスに変更すると、正しいバージョンを実装するように強制されます
  • [variable]>(type-cast)(1 << [variable2])は正しいバージョンを実装します。

これはすべてレジスタを保存しようとしているのではないかと思います。[変数]が配列アクセスの場合、インデックスも保持する必要があります。それが間違っているまで、誰かがおそらくこれはとても賢いと思ったでしょう。

バグレポートのコードを使用する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

ここでは未定義の動作について話していると確信しています。「大きい」整数を、新しい値のサイズに収まらない値の小さい整数に変換することは、私が知る限り未定義です。131072は間違いなくuint_8に適合しません。

生成されたコードを見ても、「setb」ではなく「sete」を実行するため、おそらく完全には正しくないと思います。それは私には非常に疑わしいようです。

式を変えると:

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

次に、「seta」命令を使用します。これは私が期待していることです。もう少し掘り下げますが、何かが少し間違っているようです。

于 2013-01-20T12:48:42.030 に答える