C wurde entwickelt, um die Ganzzahltypen der in Ausdrücken verwendeten Operanden implizit und stillschweigend zu ändern. Es gibt mehrere Fälle, in denen die Sprache den Compiler zwingt, entweder die Operanden in einen größeren Typ zu ändern oder ihre Signatur zu ändern.
Das Grundprinzip dahinter besteht darin, versehentliche Überläufe während der Arithmetik zu verhindern, aber auch zu ermöglichen, dass Operanden mit unterschiedlicher Vorzeichen in demselben Ausdruck nebeneinander existieren.
Leider verursachen die Regeln für die implizite Typwerbung viel mehr Schaden als Nutzen, bis zu dem Punkt, an dem sie möglicherweise einer der größten Fehler in der C-Sprache sind. Diese Regeln sind dem durchschnittlichen C-Programmierer oft nicht einmal bekannt und verursachen daher alle möglichen sehr subtilen Fehler.
In der Regel sehen Sie Szenarien, in denen der Programmierer sagt, "nur auf x umwandeln und es funktioniert" - aber er weiß nicht warum. Oder solche Fehler manifestieren sich als seltenes, intermittierendes Phänomen, das aus scheinbar einfachem und direktem Code hervorgeht. Die implizite Heraufstufung ist besonders problematisch bei Code-Manipulationen, da die meisten bitweisen Operatoren in C ein schlecht definiertes Verhalten aufweisen, wenn ein signierter Operand angegeben wird.
Ganzzahlige Typen und Konvertierungsrang
Die Integer - Typen in C sind char
, short
, int
, long
, long long
und enum
.
_Bool
/ bool
wird auch als ganzzahliger Typ behandelt, wenn es um Typwerbung geht.
Alle Ganzzahlen haben einen bestimmten Conversion-Rang . C11 6.3.1.1, Schwerpunkt auf den wichtigsten Teilen:
Jeder Integer-Typ hat einen Integer-Konvertierungsrang, der wie folgt definiert ist:
- Keine zwei vorzeichenbehafteten Integer-Typen dürfen denselben Rang haben, auch wenn sie dieselbe Darstellung haben.
- Der Rang eines vorzeichenbehafteten Ganzzahltyps muss größer sein als der Rang eines vorzeichenbehafteten Ganzzahltyps mit geringerer Genauigkeit.
- Der Rang von long long int
ist größer als der Rang von long int
, der größer sein soll als der Rang von int
, der größer sein soll als der Rang von short int
, der größer sein soll als der Rang von signed char
.
- Der Rang eines vorzeichenlosen Integer-Typs entspricht gegebenenfalls dem Rang des entsprechenden vorzeichenbehafteten Integer-Typs.
- Der Rang eines Standard-Integer-Typs muss größer sein als der Rang eines erweiterten Integer-Typs mit derselben Breite.
- Der Rang eines Zeichens entspricht dem Rang eines signierten Zeichens und eines nicht signierten Zeichens.
- Der Rang von _Bool muss niedriger sein als der Rang aller anderen Standard-Integer-Typen.
- Der Rang eines Aufzählungstyps muss dem Rang des kompatiblen Ganzzahltyps entsprechen (siehe 6.7.2.2).
Die Typen von stdint.h
sortieren auch hier mit dem gleichen Rang wie der Typ, dem sie auf dem gegebenen System entsprechen. Hat zum Beispiel int32_t
den gleichen Rang wie int
auf einem 32-Bit-System.
Ferner gibt C11 6.3.1.1 an, welche Typen als kleine ganzzahlige Typen betrachtet werden (kein formaler Begriff):
Folgendes kann in einem Ausdruck verwendet werden, wo immer ein int
oder unsigned int
verwendet werden kann:
- Ein Objekt oder Ausdruck mit einem ganzzahligen Typ (außer int
oder unsigned int
), dessen ganzzahliger Konvertierungsrang kleiner oder gleich dem Rang von int
und ist unsigned int
.
Was dieser etwas kryptische Text in der Praxis bedeutet, ist , dass _Bool
, char
und short
(und auch int8_t
, uint8_t
usw.) die „kleinen Integer - Typen“ sind. Diese werden auf besondere Weise behandelt und unterliegen einer impliziten Werbung, wie nachstehend erläutert.
Die ganzzahligen Aktionen
Immer wenn ein kleiner ganzzahliger Typ in einem Ausdruck verwendet wird, wird er implizit konvertiert, in int
den immer signiert ist. Dies wird als Ganzzahl-Heraufstufung oder Ganzzahl-Heraufstufungsregel bezeichnet .
Formal heißt es in der Regel (C11 6.3.1.1):
Wenn a int
alle Werte des ursprünglichen Typs darstellen kann (wie durch die Breite für ein Bitfeld eingeschränkt), wird der Wert in a konvertiert int
. Andernfalls wird es in ein konvertiert unsigned int
. Diese werden als Integer-Promotions bezeichnet .
Dies bedeutet, dass alle kleinen Ganzzahltypen, unabhängig von der Signatur, implizit in (signiert) konvertiert werden, int
wenn sie in den meisten Ausdrücken verwendet werden.
Dieser Text wird oft missverstanden als: "Alle kleinen, vorzeichenbehafteten Ganzzahltypen werden in vorzeichenbehaftete int und alle kleinen vorzeichenlosen Ganzzahltypen in vorzeichenlose int konvertiert." Das ist falsch. Der vorzeichenlose Teil bedeutet hier nur, dass der Operand in konvertiert wird , wenn wir beispielsweise einen unsigned short
Operanden haben und int
zufällig dieselbe Größe wie short
auf dem angegebenen System haben . Wie in passiert nichts Besonderes. Falls es sich jedoch um einen kleineren Typ als handelt , wird er immer in (signiert) konvertiert , unabhängig davon , ob der Short signiert oder nicht signiert war !unsigned short
unsigned int
short
int
int
Die harte Realität, die durch die ganzzahligen Beförderungen verursacht wird, bedeutet, dass fast keine Operation in C mit kleinen Typen wie char
oder ausgeführt werden kann short
. Operationen werden immer an int
oder größeren Typen ausgeführt.
Das mag nach Unsinn klingen, aber zum Glück darf der Compiler den Code optimieren. Zum Beispiel würde ein Ausdruck, der zwei unsigned char
Operanden enthält, die Operanden heraufstufen int
und die Operation als ausführen int
. Der Compiler kann den Ausdruck jedoch so optimieren, dass er erwartungsgemäß tatsächlich als 8-Bit-Operation ausgeführt wird. Hier kommt jedoch das Problem: Der Compiler darf die implizite Änderung der Signatur, die durch die Ganzzahl-Heraufstufung verursacht wird, nicht optimieren. Weil der Compiler nicht erkennen kann, ob der Programmierer absichtlich auf implizite Werbung angewiesen ist oder ob dies unbeabsichtigt ist.
Aus diesem Grund schlägt Beispiel 1 in der Frage fehl. Beide vorzeichenlosen Zeichenoperanden werden zum Typ heraufgestuft int
, die Operation wird für den Typ ausgeführt int
und das Ergebnis von x - y
ist vom Typ int
. Das heißt, wir bekommen -1
stattdessen 255
was erwartet worden sein könnte. Der Compiler generiert möglicherweise Maschinencode, der den Code mit 8-Bit-Anweisungen anstelle von ausführt int
, optimiert jedoch möglicherweise nicht die Änderung der Vorzeichen. Dies bedeutet, dass wir am Ende ein negatives Ergebnis haben, das wiederum zu einer seltsamen Zahl führt, wenn printf("%u
es aufgerufen wird. Beispiel 1 könnte behoben werden, indem das Ergebnis der Operation auf Typ zurückgesetzt wird unsigned char
.
Mit Ausnahme einiger Sonderfälle wie ++
und sizeof
Operatoren gelten die ganzzahligen Heraufstufungen für fast alle Operationen in C, unabhängig davon, ob unäre, binäre (oder ternäre) Operatoren verwendet werden.
Die üblichen arithmetischen Umrechnungen
Immer wenn eine binäre Operation (eine Operation mit 2 Operanden) in C ausgeführt wird, müssen beide Operanden des Operators vom gleichen Typ sein. Wenn die Operanden unterschiedlichen Typs sind, erzwingt C daher eine implizite Konvertierung eines Operanden in den Typ des anderen Operanden. Die Regeln dafür werden als die üblichen artihmetischen Konvertierungen bezeichnet (manchmal informell als "Balancing" bezeichnet). Diese sind in C11 6.3.18 angegeben:
(Stellen Sie sich diese Regel als lange, verschachtelte if-else if
Anweisung vor und sie ist möglicherweise leichter zu lesen :))
6.3.1.8 Übliche arithmetische Umrechnungen
Viele Operatoren, die Operanden vom arithmetischen Typ erwarten, verursachen Konvertierungen und ergeben auf ähnliche Weise Ergebnistypen. Der Zweck besteht darin, einen gemeinsamen reellen Typ für die Operanden und das Ergebnis zu bestimmen. Für die angegebenen Operanden wird jeder Operand ohne Änderung der Typdomäne in einen Typ konvertiert, dessen entsprechender Realtyp der gemeinsame Realtyp ist. Sofern nicht ausdrücklich anders angegeben, ist der gemeinsame Realtyp auch der entsprechende Realtyp des Ergebnisses, dessen Typdomäne die Typdomäne der Operanden ist, wenn sie gleich sind, und ansonsten komplex. Dieses Muster heißt übliche arithmetische Umrechnung bezeichnet :
Hierbei ist zu beachten, dass die üblichen arithmetischen Konvertierungen sowohl für Gleitkomma- als auch für Ganzzahlvariablen gelten. Bei Ganzzahlen können wir auch feststellen, dass die Ganzzahl-Promotions innerhalb der üblichen arithmetischen Konvertierungen aufgerufen werden. Und danach, wenn beide Operanden mindestens den Rang habenint
, werden die Operatoren auf den gleichen Typ mit der gleichen Vorzeichen ausgeglichen.
Dies ist der Grund, warum a + b
in Beispiel 2 ein seltsames Ergebnis erzielt wird. Beide Operanden sind Ganzzahlen und mindestens von Rang int
, sodass die Ganzzahl-Promotions nicht gelten. Die Operanden sind nicht vom gleichen Typ - a
ist unsigned int
und b
ist signed int
. Daher wird der Operator b
vorübergehend in Typ konvertiertunsigned int
. Während dieser Konvertierung verliert es die Vorzeicheninformationen und endet als großer Wert.
Der Grund, warum das Ändern des Typs short
in Beispiel 3 das Problem behebt, liegt darin, dass short
es sich um einen kleinen Ganzzahltyp handelt. Dies bedeutet, dass beide Operanden eine Ganzzahl sind, int
die zu einem signierten Typ heraufgestuft wird. Nach der Ganzzahl-Heraufstufung haben beide Operanden den gleichen Typ ( int
), es ist keine weitere Konvertierung erforderlich. Und dann kann die Operation wie erwartet an einem signierten Typ ausgeführt werden.
short
enger ist alsint
(oder mit anderen Worten, es wird angenommen, dassint
alle Werte von dargestellt werden könnenunsigned short
).