Optimierungsstufen in gcc ändern das Verhalten des c-Programms

Lesezeit: 1 Minute

Benutzer-Avatar
Benutzer1142769

Ich sehe ein Verhalten, das ich nicht erwarte, wenn ich diesen Code mit unterschiedlichen Optimierungsstufen in gcc kompiliere.

Der Funktionstest sollte eine 64-Bit-Ganzzahl ohne Vorzeichen mit Einsen füllen, sie um shift_size-Bits nach links verschieben und die 32 niedrigen Bits als eine 32-Bit-Ganzzahl ohne Vorzeichen zurückgeben.

Wenn ich mit -O0 kompiliere, erhalte ich die erwarteten Ergebnisse.

Wenn ich mit -O2 kompiliere, mache ich das nicht, wenn ich versuche, um 32 Bit oder mehr zu verschieben.

Tatsächlich erhalte ich genau die Ergebnisse, die ich erwarten würde, wenn ich eine 32-Bit-Ganzzahl um Verschiebungen verschieben würde, die größer oder gleich der Bitbreite auf x86 sind, was eine Verschiebung ist, die nur die 5 niedrigen Bits der Verschiebungsgröße verwendet.

Aber ich verschiebe eine 64-Bit-Zahl, also sollten Verschiebungen <64 legal sein, oder?

Ich nehme an, es ist ein Fehler in meinem Verständnis und nicht im Compiler, aber ich konnte es nicht herausfinden.

Meine Maschine: gcc (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 i686-linux-gnu

#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>

uint32_t test(unsigned int shift_size) {
    uint64_t res = 0;
    res = ~res;
    res = res << shift_size; //Shift size < uint64_t width so this should work
    return res; //Implicit cast to uint32_t
}

int main(int argc, char *argv[])
{
    int dst;
    sscanf(argv[1], "%d", &dst); //Get arg from outside so optimizer doesn't eat everything
    printf("%" PRIu32 "l\n", test(dst));
    return 0;
}

Verwendungszweck:

$ gcc -Wall -O0 test.c 
$ ./a.out 32
0l
$ gcc -Wall -O2 test.c 
$ ./a.out 32
4294967295l

gcc -S -Wall -O0 test.c

gcc -S -Wall -O2 test.c

  • Könnten Sie auch die relevanten Teile des von Ihrem Compiler generierten Assemblercodes posten? (-S für gcc)

    – Greg Hewgill

    11. Januar 2012 um 9:50 Uhr


  • FWIW gibt es das richtige Ergebnis (0l) für mich auf allen Optimierungsstufen ab -O0 zu -O3 mit gcc 4.2.1, also vermute ich Sie könnte habe einen gcc-bug gefunden.

    – PaulR

    11. Januar 2012 um 9:52 Uhr

  • hm, Code funktioniert bei mir in beiden Optimierungsstufen einwandfrei … (gcc Version 4.5.0 20100604, openSUSE 11.3 (x86_64))

    – Bort

    11. Januar 2012 um 9:54 Uhr

  • @Alex: wenn du das versuchst Nicht-LLVM gcc 4.2.1 mit -O2 -m32 dann sollten Sie es sehen (das ist, was ich verwende)

    – PaulR

    11. Januar 2012 um 10:02 Uhr

  • Es ist ein bestätigter Fehler: gcc.gnu.org/bugzilla/show_bug.cgi?id=51821

    – Nr

    11. Januar 2012 um 11:34 Uhr

Benutzer-Avatar
pmg

"%u" (oder "%lu") und uint32_t sind nicht unbedingt kompatibel. Versuchen

    #include <inttypes.h>

    //printf("%ul\n", test(dst));
    printf("%" PRIu32 "l\n", test(dst));

Drucken a uint32_t Wert mit a "%u" (oder "%lu")-Spezifizierer kann Undefiniertes Verhalten aufrufen.

  • Guter Aufruf, uint32_t darf kein unsigned int sein.

    – Traumlax

    11. Januar 2012 um 10:18 Uhr

  • @dreamlax: Ich denke, der Punkt ist, dass das OP versehentlich “%ul” anstelle von “%lu” verwendet hat, was letztendlich von printf als “%u” genommen wird. Die Verwendung von PRIu32 beseitigt die Möglichkeit, einen falschen Bezeichner zu verwenden.

    – Blechmann

    11. Januar 2012 um 10:21 Uhr

Benutzer-Avatar
Greg Hewgill

Dies konnte ich reproduzieren. Hier ist das relevante Bit des generierten Codes mit -O2:

    movl    $-1, %eax
    movl    $-1, %edx
    sall    %cl, %eax
    xorl    %edx, %edx
    testb   $32, %cl
    cmovne  %eax, %edx
    cmovne  %edx, %eax    ; This appears to be the instruction in error.
                          ; It looks as though gcc thought that %edx might still
                          ; be zero at this point, because if the shift count is
                          ; >= 32 then %eax should be zero after this.

und hier ist das entsprechende Bit mit -O0:

    movl    -16(%ebp), %eax
    movl    -12(%ebp), %edx
    shldl   %cl,%eax, %edx
    sall    %cl, %eax
    testb   $32, %cl
    je      L3
    movl    %eax, %edx
    xorl    %eax, %eax         ; correctly zeros %eax if shift count >= 32
L3:
    movl    %eax, -16(%ebp)
    movl    %edx, -12(%ebp)

Compiler ist:

i686-apple-darwin11-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5666) (dot 3)

Danke, dass du deine gepostet hast gcc -S Ausgang. Ich habe es mir angesehen und obwohl es etwas anders ist, hat der kritische Teil den gleichen Fehler wie das, was ich auf meiner Maschine gesehen habe.

Es sieht so aus als ob könnte für mich ein 32-Bit-spezifischer Compiler-Fehler sein. Mit dem Code aus der Frage und gcc 4.2.1 kann ich den Fehler reproduzieren solange ich mit kompiliere gcc -m32 -O2 ....

Wenn ich jedoch ein Debug-Printf hinzufüge:

uint32_t test(unsigned int shift_size) {
    uint64_t res = 0;
    res = ~res;
    res = res << shift_size; //Shift size < uint64_t width so this should work
    printf("res = %llx\n", res);
    return res; //Implicit cast to uint32_t
}

dann ist das problem weg.

Der nächste Schritt wäre, sich den generierten Code anzusehen, um zu versuchen, den Fehler zu identifizieren/zu bestätigen.

  • Als vorübergehende Problemumgehung könnte man vielleicht drucken /dev/null: FILE *null; null = fopen("/dev/null","w"); fprintf(null,"res = %llx\n", res); fclose(null);

    – Alex Reynolds

    11. Januar 2012 um 10:10 Uhr


  • Als vorübergehende Problemumgehung machen res volatile funktioniert genauso gut und erfordert keine Dateioperationen.

    Benutzer784668

    11. Januar 2012 um 11:34 Uhr


Es sieht aus wie ein Fehler. Mein erraten liegt daran, dass der Compiler die letzten beiden Zeilen gefaltet hat aus:

res = res << shift_size
return (uint32_t)res;

hinein:

return ((uint32_t)res) << shift_size;

Letzteres ist jetzt für 32 oder mehr gut definiert.

Ich erinnere mich nicht, was C99 gesagt hat, aber es scheint, dass in gcc uint32_t enthalten ist wenigstens 32 Bit und kann mehr enthalten. Wenn Sie es also optimieren, verwendet es 64-Bit-Variable, was auf 64-Bit-Computern schneller ist.

  • Nein, uint32_t , falls verfügbar, hat genau 32 Bit. (uint_fast32_t oder uint_least32_t könnten größer sein, aber nicht uint32_t)

    – Nr

    11. Januar 2012 um 10:30 Uhr

  • uint64_t ist nicht in allen Fällen schneller als uint32_t: Es ist schneller für die Indizierung in Arrays, wenn Sie einen 64-Bit-Adressraum auf Intel/Amd64-Rechnern haben, da 16-Bit- und 32-Bit-Indizes konvertiert werden müssen, während 8-Bit und 64-Bit in dieser Assembler-Sprache zulässige Offsets sind . aber Multiplikation und Division sind langsamer, wenn Sie größere Bitgrößen verwenden.

    – Komonade

    11. Januar 2012 um 14:12 Uhr

  • Nein, uint32_t , falls verfügbar, hat genau 32 Bit. (uint_fast32_t oder uint_least32_t könnten größer sein, aber nicht uint32_t)

    – Nr

    11. Januar 2012 um 10:30 Uhr

  • uint64_t ist nicht in allen Fällen schneller als uint32_t: Es ist schneller für die Indizierung in Arrays, wenn Sie einen 64-Bit-Adressraum auf Intel/Amd64-Rechnern haben, da 16-Bit- und 32-Bit-Indizes konvertiert werden müssen, während 8-Bit und 64-Bit in dieser Assembler-Sprache zulässige Offsets sind . aber Multiplikation und Division sind langsamer, wenn Sie größere Bitgrößen verwenden.

    – Komonade

    11. Januar 2012 um 14:12 Uhr

1229680cookie-checkOptimierungsstufen in gcc ändern das Verhalten des c-Programms

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy