2015-07-27 13 views
18

Mit meinem Compiler ist c 54464 (16 Bit abgeschnitten) und d ist 10176. Aber mit gcc, c ist 120000 und d ist 600000c = a + b und implizite Konvertierung

Was das wahre Verhalten ? Ist das Verhalten nicht definiert? Oder ist mein Compiler falsch?

unsigned short a = 60000; 
unsigned short b = 60000; 
unsigned long c = a + b; 
unsigned long d = a * 10; 

Gibt es eine Option, in diesen Fällen zu warnen?

Wconversion warnt auf:

void foo(unsigned long a); 
foo(a+b); 

aber warnen nicht auf:

unsigned long c = a + b 
+6

Bezieht sich auf [Was passiert, wenn ein Integer-Überlauf in einem C-Ausdruck auftritt?] (Http://Stackoverflow.com/q/26195811/1708801) siehe auch [Warum muss ein Short vor arithmetischen Operationen in int konvertiert werden C und C++?] (Http://stackoverflow.com/q/24371868/1708801) –

+2

Was ist 'Ihr Compiler'? Es sieht aus wie ein eingebetteter c-Compiler. Bessere Ansicht einer eingebetteten Toolchain speziell – HuStmpHrrr

+0

Wenn Sie nicht einen eingebetteten Compiler verwenden, verwenden Sie wahrscheinlich Turbo C, die kein "echter" C-Compiler ist. Entfernen Sie es so schnell wie möglich –

Antwort

17

Zunächst sollten Sie wissen, dass in C die Standardtypen keine spezifische Genauigkeit (Anzahl der darstellbaren Werte) für die Standard-Integer-Typen haben. Es erfordert nur eine minimale Präzision für jeden Typ. Diese ergeben sich in den folgenden typischen Bit Größen, die standard ermöglichen komplexere Darstellungen:

  • char: 8 Bit
  • short: 16 Bit
  • int: 16 (!) Bits
  • long: 32 bits
  • long long (ab C99): 64 Bits

Anmerkung: Die tatsächlichen Grenzen (die eine gewisse Präzision) eine Implementierung implizieren in limits.h gegeben.

Zweitens wird der Typ einer Operation von den Typen der Operanden bestimmt, nicht vom Typ der linken Seite einer Zuweisung (weil Zuweisungen auch nur Ausdrücke sind). Dazu sind die oben angegebenen Typen nach Umrechnungsrang sortiert. Operanden mit kleinerem Rang als int werden zuerst in int konvertiert. Bei anderen Operanden wird der mit dem kleineren Rang in den Typ des anderen Operanden konvertiert. Dies sind die usual arithmetic conversions.

Ihre Implementierung scheint 16-Bit-unsigned int mit der gleichen Größe wie unsigned short zu verwenden, so a und b-unsigned int umgewandelt wird, wird der Betrieb mit 16 Bit ausgeführt. Für unsigned wird die Operation modulo 65536 (2 hoch 16) durchgeführt - dies wird Wrap-around genannt (dies ist nicht erforderlich für signierte Typen!). Das Ergebnis wird dann in unsigned long konvertiert und den Variablen zugewiesen.

Für gcc nehme ich an, das kompiliert für einen PC oder eine 32-Bit-CPU. hierfür hat (unsigned) int typischerweise 32 Bit, während (unsigned) long mindestens 32 Bit (erforderlich) hat. Es gibt also keine Umgehung für die Operationen.

Hinweis: Für den PC werden die Operanden zu int umgewandelt, nicht unsigned int. Dies, weil int bereits alle Werte von unsigned short darstellen kann; unsigned int ist nicht erforderlich. Dies kann zu einem unerwarteten (tatsächlich: Implementierung definierten) Verhalten führen, wenn das Ergebnis der Operation einen signed int überläuft!

Wenn Sie Typen definierter Größe benötigen, siehe stdint.h (seit C99) für uint16_t, uint32_t. Diese sind typedef s zu Typen mit der entsprechenden Größe für Ihre Implementierung.

können Sie werfen auch einen der Operanden (nicht der ganze Ausdruck!) Auf die Art des Ergebnisses:

unsigned long c = (unsigned long)a + b; 

oder Arten von bekannter Größe mit:

#include <stdint.h> 
... 
uint16_t a = 60000, b = 60000; 
uint32_t c = (uint32_t)a + b; 

Beachten Sie, dass Aufgrund der Konvertierungsregeln ist das Umwandeln eines Operanden ausreichend.

aktualisieren (dank @chux):

Die Besetzung oben arbeitet gezeigt ohne Probleme. Wenn a jedoch einen höheren Konvertierungsrang als die Typumwandlung aufweist, wird der Wert möglicherweise auf den kleineren Typ reduziert.Während dies kann leicht als alle Typen zur Compile-Zeit (statische Typisierung) bekannt sind, vermieden werden, eine Alternative ist mit 1 der gewünschten Art zu multiplizieren:

unsigned long c = ((unsigned long)1U * a) + b 

diese Weise wird der größere Rang der in dem gegebenen Cast oder a (oder b) wird verwendet. Die Multiplikation wird von jedem vernünftigen Compiler eliminiert.

Ein weiterer Ansatz, die Vermeidung auch die Zieltypnamen kennen, kann mit der typeof() gcc Erweiterung erfolgen:

unsigned long c; 

... many lines of code 

c = ((typeof(c))1U * a) + b 
+1

Der Übergang von limits.h zur Tabelle der Bereiche war ein wenig abrupt. Auf den Unterschied eingehen: Der Standard listet darstellbare Bereiche auf. Die Tabelle der Größen, die Sie angeben, ist kompatibel mit dem Standard. limits.h beschreibt die Grenzen der Compiler-Implementierung und es ist vernünftig zu erwarten, dass sie die Werte in Ihrer Tabelle erfüllt oder überschreitet. –

+1

Detail: '(uint32_t) a + b;' kann zu einem _narrowing_ von 'a' führen. (nicht in diesem trivialen Beispiel). _Im Allgemeinen_, um eine ganze Zahl zu erweitern, schlagen Sie vor, mit "1" des Typs wie in "1UL * a + b;" zu multiplizieren anstatt zu werfen. Dieses Apraoch wird niemals zu einer Verengung von "a" führen. – chux

+0

@chux: Danke, ich habe meine Antwort aktualisiert. – Olaf

6

a + b wird als unsigned int berechnet werden (die Tatsache, dass es zu einem unsigned long zugewiesen wird, ist nicht relevant) . Der C-Standard schreibt vor, dass diese Summe um modulo "eins plus das größte unsigned mögliche" wickeln wird. Auf Ihrem System, es sieht aus wie ein unsigned int 16 Bit sind, so ist das Ergebnis Modulo 65536.

auf dem anderen System berechnet wird, sieht es aus wie int und unsigned int größer sind, und daher in der Lage, die größeren Zahlen zu halten. Was jetzt passiert, ist ziemlich subtil (quittieren Sie @PascalCuoq): Da alle Werte von unsigned short in int darstellbar sind, wird a + b als int berechnet. (Nur wenn short und int sind die gleiche Breite oder in irgendeiner anderen Art und Weise, einige Werte von unsigned short nicht dargestellt werden kann als int die Summe wird als unsigned int berechnet werden.)

Obwohl der C-Standard spezifiziert nicht, eine feste Größe entweder für eine unsigned short oder eine unsigned int, ist Ihr Programm Verhalten gut definiert. Beachten Sie, dass dies nicht True für einen signierten Typ ist.

Als abschließende Bemerkung, können Sie die Größe Typen verwenden uint16_t, uint32_t etc., die, wenn sie von Ihrem Compiler unterstützt wird, sind garantiert die angegebene Größe haben.

+0

Beachten Sie, dass, wenn das Programm "a * b" multipliziert hätte, anstatt sie zu addieren, das Ergebnis nicht auf Plattformen definiert würde, bei denen "int" größer als "unsigned short" aber nicht mehr als doppelt so groß ist. – supercat

3

In C die Typen char, short (und ihre unsigned couterparts) und float sollten in Betracht gezogen werden als "Speicher" -Typen, da sie entworfen wurden, um den Speicher zu optimieren, aber sind nicht die "native" Größe, die die CPU bevorzugt und sie werden nie für Berechnungen verwendet. Wenn Sie beispielsweise zwei char Werte haben und diese in einen Ausdruck setzen, werden sie zuerst in int konvertiert, dann wird der Vorgang ausgeführt. Der Grund ist, dass die CPU besser mit int arbeitet. Das gleiche passiert für float, das immer implizit in eine double für Berechnungen konvertiert wird.

In Ihrem Code ist die Berechnung eine Summe von zwei vorzeichenlosen Ganzzahlen; in C gibt es keine Möglichkeit, die Summe von zwei unsigned Shorts zu berechnen ... was Sie tun können, ist speichern das Endergebnis in einem unsigned kurz, dass, dank der Eigenschaften von Modulo Mathe, wird das gleiche sein.