6 Stimmen

Sprache C: #DEFINEd value bringt die 8-Bit-Multiplikation durcheinander. Warum?

Ich habe den folgenden C-Code:

#define PRR_SCALE 255
...
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
printf("prr: %u\n", prr);

Wenn ich dies kompiliere (mit einem msp430-Plattform-Compiler, für ein kleines eingebettetes Betriebssystem namens contiki ) ist das Ergebnis 0, während ich 191 erwartet hatte. (uint8_t ist als unsigned char typisiert)

Wenn ich es ändere in:

uint8_t a = 3;
uint8_t b = 4;
uint8_t c = 255;
uint8_t prr;
prr = (c * a) / b;
printf("prr: %u\n", prr);

er funktioniert korrekt und druckt 191.

Das Kompilieren einer einfachen Version dieses Programms mit gcc auf einem Ubuntu-Rechner ergibt in beiden Fällen den richtigen Wert.

Ich bin mir nicht ganz sicher, warum das so ist. Ich könnte es umgehen, indem ich den DEFINEd-Wert vorher einer Variablen zuordne, aber das würde ich lieber nicht tun.

Weiß jemand, warum das so ist? Vielleicht mit einem Link zu weiteren Informationen darüber?

10voto

avakar Punkte 31197

Die kurze Antwort: Ihr Compiler ist fehlerhaft (es gibt kein Problem mit dem Überlauf, wie andere vorgeschlagen haben).

In beiden Fällen wird die Arithmetik in int die garantiert mindestens 16 Bit lang ist. Im ersten Schnipsel ist dies der Fall, weil 255 ist ein int im letzteren Fall ist es wegen ganzheitliche Förderung .

Wie Sie bemerkten, behandelt gcc dies korrekt.

2voto

Richard Punkte 1129

255 wird als Integer-Literal verarbeitet und bewirkt, dass der gesamte Ausdruck auf int und nicht auf unsigned char basiert. Im zweiten Fall wird der korrekte Typ erzwungen. Versuchen Sie, Ihre #define wie folgt zu ändern:

 #define PRR_SCALE ((uint8_t) 255)

2voto

Wenn es sich bei dem betreffenden Compiler um den mspgcc handelt, sollte er ein Assembler-Listing des kompilierten Programms zusammen mit der Binär-/Hex-Datei ausgeben. Andere Compiler benötigen dazu möglicherweise zusätzliche Compiler-Flags. Oder vielleicht sogar einen separaten Disassembler, der auf die Binärdatei angewendet wird.

Hier ist der richtige Ort, um nach einer Erklärung zu suchen. Aufgrund von Compiler-Optimierungen hat der tatsächliche Code, der dem Prozessor präsentiert wird, möglicherweise nicht viel Ähnlichkeit mit dem ursprünglichen C-Code (erfüllt aber normalerweise die gleiche Aufgabe).

Wenn Sie die wenigen Assembler-Anweisungen durchgehen, die den fehlerhaften Code darstellen, sollten Sie die Ursache des Problems herausfinden.

Ich vermute, dass der Compiler die gesamte Berechnung irgendwie optimiert, da die definierte Konstante zur Kompilierungszeit ein bekannter Teil ist. 255*x könnte zu x<<8-x optimiert werden (was schneller und kleiner ist). Vielleicht läuft etwas mit dem optimierten Assembler-Code schief.

Ich habe mir die Zeit genommen, beide Versionen auf meinem System zu kompilieren. Mit aktiver Optimierung erzeugt der mspgcc den folgenden Code:

#define PRR_SCALE 255
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
    40ce:   3c 40 fd ff     mov #-3,    r12 ;#0xfffd
    40d2:   2a 42           mov #4, r10 ;r2 As==10
    40d4:   b0 12 fa 6f     call    __divmodhi4 ;#0x6ffa
    40d8:   0f 4c           mov r12,    r15 ;
printf("prr: %u\n", prr);
    40da:   7f f3           and.b   #-1,    r15 ;r3 As==11
    40dc:   0f 12           push    r15     ;
    40de:   30 12 c0 40     push    #16576      ;#0x40c0
    40e2:   b0 12 9c 67     call    printf      ;#0x679c
    40e6:   21 52           add #4, r1  ;r2 As==10

Wie wir sehen können, berechnet der Compiler das Ergebnis von 255*3 direkt zu -3 (0xfffd). Und hier liegt das Problem. Irgendwie wird die 255 als -1 vorzeichenbehaftetes 8-Bit anstelle von 255 vorzeichenlosen 16-Bit interpretiert. Oder es wird zuerst in 8 Bit geparst und dann auf 16 Bit vorzeichenerweitert oder was auch immer.

Eine Diskussion zu diesem Thema wurde bereits auf der Mailingliste mspgcc gestartet.

1voto

Andy White Punkte 83877

Ich bin mir nicht sicher, warum die Definition nicht funktioniert, aber es könnte sein, dass Sie mit der uint8_t Variablen. 255 ist der Höchstwert für uint8_t (2^8 - 1) Wenn Sie das mit 3 multiplizieren, werden Sie zwangsläufig mit einigen subtilen Überschneidungsproblemen konfrontiert.

Der Compiler könnte Ihren Code optimieren und das Ergebnis Ihres mathematischen Ausdrucks vorberechnen und das Ergebnis in prr schieben (da es passt, obwohl der Zwischenwert nicht passt).

Prüfen Sie, was passiert, wenn Sie Ihren Ausdruck wie folgt aufteilen (dies wird sich nicht so verhalten, wie Sie es wünschen):

prr = c * a; // rollover!
prr = prr / b;

Möglicherweise müssen Sie einfach einen größeren Datentyp verwenden.

0voto

Alphaneo Punkte 11361

Ein Unterschied, den ich mir in Fall 1 vorstellen kann, ist,

Der PRR_SCALE-Literalwert kann in den ROM- oder Code-Bereich gehen. Und es kann einige Unterschiede im MUL-Operationscode geben, zum Beispiel,

case-1: [register], [rom]
case -2: [register], [register]

Das macht vielleicht gar keinen Sinn.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X