2015-08-14 6 views
15

Warum wird der folgende Code gedruckt 255?Warum gibt ein Modulo-Vorgang einen unerwarteten Wert zurück?

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

int main(void) { 
    uint8_t i = 0; 
    i = (i - 1) % 16; 
    printf("i: %d\n", i); 
    return 0; 
} 

I 15 angenommen, obwohl i - 1 in eine Ganzzahl auswertet.

+1

uint - unsigned int, 0-1 wird zu MAX_INT –

+2

@MarcB: '0 - 1' ist' -1'. 'MAX_INT' ist'> = 32767'. – Olaf

+1

@olaf: Es ist ein unsigned int –

Antwort

14

Wegen integer promotions in der C-Norm. Kurz: Jeder Typ "kleiner" als int wird vor der Verwendung in int konvertiert. Sie können dies im Allgemeinen nicht vermeiden.

So was geht weiter:i wird zu int befördert. Der Ausdruck wird als int ausgewertet (die verwendeten Konstanten sind ebenfalls int). Der Modul ist -1. Dies wird dann durch die Zuweisung in uint8_t: 255 umgewandelt.

Für printf dann i ist integer-gefördert int (wieder): (int)255. Dies schadet jedoch nicht.

Beachten Sie, dass in C89 für , a % b nicht unbedingt negativ ist. Es wurde implementiert und könnte 15 sein. Da C99, -1 % 16 jedoch garantiert -1 ist, muss die Division den algebraischen Quotienten ergeben.


Wenn Sie das Modul ein positives Ergebnis überprüfen wollen, müssen Sie den gesamten Ausdruck auswerten unsigned von i Gießen:

i = ((unsigned)i - 1) % 16; 

Empfehlung: Compiler-Warnungen aktivieren. Zumindest sollte die Konvertierung für die Zuweisung eine Abbruchwarnung geben.

+4

Es ist nur die Implementierung definiert in C89/C90. Der verlinkte C11-Standard-Absatz definiert ihn sogar im zweiten Satz und der UB-Code ist hier nicht relevant. – cremno

+2

Dann lesen Sie den ersten Satz einschließlich der Fußnote. Ihr zweites Beispiel (insbesondere "a/16 == -1") erfüllt es nicht. Die C99-Begründung (6.5.5 auf Seite 67, 7.20.6 zu 164 und 165) ist ebenfalls eine hilfreiche Ressource. – cremno

+0

In Bezug auf die Abschneidewarnung ist '((vorzeichenlos) i - 1)% 16 'garantiert im Bereich von 0 bis 15 zu liegen, also sollte * keine Warnung sein, um es einem' uint8_t 'zuzuweisen, weil der Wert nicht sein kann außer Reichweite. –

12

Dies liegt daran, -1 % n-1 zurückkehren würde und NICHT n - 11. Da i in diesem Fall nicht signiert sind 8-Bit-int, wird es 255.

1See this question for more details, wie Modulo für negative ganze Zahlen arbeitet in C/C++.

+1

Das stimmt, aber das OP erwartet anscheinend, dass sich 'uint8_t' als * 8-Bit unsigned * Typ während des gesamten Auswerteprozesses verhält und vor dem * Berechnen des Modulo '255' anstelle von '-1' * erzeugt. Der Grund dafür, dass "uint8_t" sich nicht so verhält, ist, dass es zuerst zu "int" hochgestuft wird. – AnT

+0

Downvoting, weil dies nicht erklärt, warum es "-1% n" anstelle von "255% n" ist (Ganzzahl-Promotion-Regeln, siehe Olafs vollständigere Antwort). –

2

Das funktioniert (Displays 15) mit Microsoft C-Compiler (ohne stdint.h, so habe ich eine typedef):

#include <stdio.h> 
typedef unsigned char uint8_t; 

int main(void) { 
    uint8_t i = 0; 
    i = (uint8_t)(i - 1) % 16; 
    printf("i: %d\n", i); 
    return 0; 
} 

Der Grund für die 255 ist, weil (i - 1) gefördert wird, in Integer , und die Ganzzahldivision, die für% in C verwendet wird, rundet auf Null statt auf die negative Unendlichkeit ab (das Runden in Richtung der negativen Unendlichkeit ist die Art, wie es in Mathematik, Naturwissenschaften und anderen Programmiersprachen gemacht wird). Also für C% ist Null oder hat das gleiche Vorzeichen wie der Dividende (in diesem Fall -1% 16 == -1), während in Mathematik Modulo ist Null oder hat das gleiche Vorzeichen wie der Divisor.