Dieser Beitrag soll als FAQ zur impliziten Ganzzahl-Heraufstufung in C verwendet werden, insbesondere zur impliziten Heraufstufung, die durch die üblichen arithmetischen Konvertierungen und / oder die Ganzzahl-Heraufstufungen verursacht wird.
Beispiel 1)
Warum ergibt dies eine seltsame, große Ganzzahl und nicht 255?
unsigned char x = 0;
unsigned char y = 1;
printf("%u\n", x - y);
Beispiel 2)
Warum ergibt dies "-1 ist größer als 0"?
unsigned int a = 1;
signed int b = -2;
if(a + b > 0)
puts("-1 is larger than 0");
Beispiel 3)
Warum wird der Typ im obigen Beispiel short
geändert , um das Problem zu beheben?
unsigned short a = 1;
signed short b = -2;
if(a + b > 0)
puts("-1 is larger than 0"); // will not print
(Diese Beispiele waren für einen 32- oder 64-Bit-Computer mit 16-Bit-Kurzschluss gedacht.)
short
enger ist alsint
(oder mit anderen Worten, es wird angenommen, dassint
alle Werte von dargestellt werden könnenunsigned short
).printf("%u\n", x - y);
dass undefiniertes Verhalten verursacht wird~((u8)(1 << 7))
die Liste.Antworten:
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
undenum
._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:
Die Typen von
stdint.h
sortieren auch hier mit dem gleichen Rang wie der Typ, dem sie auf dem gegebenen System entsprechen. Hat zum Beispielint32_t
den gleichen Rang wieint
auf einem 32-Bit-System.Ferner gibt C11 6.3.1.1 an, welche Typen als kleine ganzzahlige Typen betrachtet werden (kein formaler Begriff):
Was dieser etwas kryptische Text in der Praxis bedeutet, ist , dass
_Bool
,char
undshort
(und auchint8_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):
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 undint
zufällig dieselbe Größe wieshort
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 kannshort
. Operationen werden immer anint
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 heraufstufenint
und die Operation als ausführenint
. 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ührtint
und das Ergebnis vonx - y
ist vom Typint
. Das heißt, wir bekommen-1
stattdessen255
was erwartet worden sein könnte. Der Compiler generiert möglicherweise Maschinencode, der den Code mit 8-Bit-Anweisungen anstelle von ausführtint
, 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, wennprintf("%u
es aufgerufen wird. Beispiel 1 könnte behoben werden, indem das Ergebnis der Operation auf Typ zurückgesetzt wirdunsigned char
.Mit Ausnahme einiger Sonderfälle wie
++
undsizeof
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 :))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 haben
int
, 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 Rangint
, sodass die Ganzzahl-Promotions nicht gelten. Die Operanden sind nicht vom gleichen Typ -a
istunsigned int
undb
istsigned int
. Daher wird der Operatorb
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, dassshort
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.quelle
x - y
die Frage in der Frage wie(unsigned)(int)((int)x - (int)y)
anstelle von(unsigned)(int)((Uchar)((Uchar)x - (Uchar)y))
goo.gl/nCvJy5 verhält . Wo sagt der Standard, dass wennx
char ist, dann+x
istint
(oder nicht signiert)? In c ++ ist es §5.3.1.7 goo.gl/FkEakX-
Operator 6.5.3.3: "Das Ergebnis des unären Operators ist das Negativ seines (heraufgestuften) Operanden. Die ganzzahligen Heraufstufungen werden für den Operanden ausgeführt, und das Ergebnis hat den heraufgestuften Typ." Ein weiteres Beispiel (Sonderfall) ist das Präfix++
, in dem keine Ganzzahl-Heraufstufung erwähnt wird, und daher wird sein Operand nicht heraufgestuft.x-y
. (Mit anderen Worten, es gibt keinen oder(char)-(char)
nur einen Operator(int)-(int)
- soweit ich weiß, ist das wahr). c11 6.5.6 / 4 verweist auf 6.3.1.8/1, in dem steht, dass "der Zweck darin besteht, den gemeinsamen realen Typ zu bestimmen". Wenn jedoch Operanden bereits vom gleichen Typ sind, ist es nicht explizit, dass eine Konversation durchgeführt werden sollte.(unsigned char)
, von dem die Operanden(unsigned char) (x-y)
ausgegangen sind , wie in : Dies gibt dem OP die erwarteten 255. Die Leute schätzen das Casting auf eine kleinere Größe oft nicht, dies ist jedoch der richtige Weg um eine Kürzung zu erreichen (gefolgt von einer impliziten / automatischen vorzeichenbehafteten oder Null-Erweiterung auf ~ int-Größe).Gemäß dem vorherigen Beitrag möchte ich zu jedem Beispiel weitere Informationen geben.
Beispiel 1)
int main(){ unsigned char x = 0; unsigned char y = 1; printf("%u\n", x - y); printf("%d\n", x - y); }
Da vorzeichenloses Zeichen kleiner als int ist, wenden wir die ganzzahlige Heraufstufung auf sie an. Dann haben wir (int) x- (int) y = (int) (- 1) und vorzeichenloses int (-1) = 4294967295.
Die Ausgabe des obigen Codes: (wie erwartet)
4294967295 -1
Wie man es repariert?
Ich habe versucht, was im vorherigen Beitrag empfohlen wurde, aber es funktioniert nicht wirklich. Hier ist der Code, der auf dem vorherigen Beitrag basiert:
Ändern Sie einen von ihnen in unsigned int
int main(){ unsigned int x = 0; unsigned char y = 1; printf("%u\n", x - y); printf("%d\n", x - y); }
Da x bereits eine vorzeichenlose Ganzzahl ist, wenden wir die Ganzzahl-Heraufstufung nur auf y an. Dann erhalten wir (unsigned int) x- (int) y. Da sie immer noch nicht den gleichen Typ haben, wenden wir die üblichen arithmetischen Konvertierungen an. Wir erhalten (unsigned int) x- (unsigned int) y = 4294967295.
Die Ausgabe des obigen Codes: (wie erwartet):
4294967295 -1
In ähnlicher Weise erhält der folgende Code das gleiche Ergebnis:
int main(){ unsigned char x = 0; unsigned int y = 1; printf("%u\n", x - y); printf("%d\n", x - y); }
Ändern Sie beide in unsigned int
int main(){ unsigned int x = 0; unsigned int y = 1; printf("%u\n", x - y); printf("%d\n", x - y); }
Da beide int ohne Vorzeichen sind, ist keine Ganzzahl-Heraufstufung erforderlich. Durch die übliche arithmetische Konvertierung (haben den gleichen Typ), (vorzeichenloses int) x- (vorzeichenloses int) y = 4294967295.
Die Ausgabe des obigen Codes: (wie erwartet):
4294967295 -1
Eine der möglichen Möglichkeiten, den Code zu korrigieren: (Fügen Sie am Ende eine Typumwandlung hinzu)
int main(){ unsigned char x = 0; unsigned char y = 1; printf("%u\n", x - y); printf("%d\n", x - y); unsigned char z = x-y; printf("%u\n", z); }
Die Ausgabe des obigen Codes:
4294967295 -1 255
Beispiel 2)
int main(){ unsigned int a = 1; signed int b = -2; if(a + b > 0) puts("-1 is larger than 0"); printf("%u\n", a+b); }
Da beide Ganzzahlen sind, ist keine Ganzzahl-Heraufstufung erforderlich. Durch die übliche arithmetische Konvertierung erhalten wir (unsigned int) a + (unsigned int) b = 1 + 4294967294 = 4294967295.
Die Ausgabe des obigen Codes: (wie erwartet)
-1 is larger than 0 4294967295
Wie man es repariert?
int main(){ unsigned int a = 1; signed int b = -2; signed int c = a+b; if(c < 0) puts("-1 is smaller than 0"); printf("%d\n", c); }
Die Ausgabe des obigen Codes:
-1 is smaller than 0 -1
Beispiel 3)
int main(){ unsigned short a = 1; signed short b = -2; if(a + b < 0) puts("-1 is smaller than 0"); printf("%d\n", a+b); }
Das letzte Beispiel hat das Problem behoben, da a und b aufgrund der Ganzzahl-Heraufstufung beide in int konvertiert wurden.
Die Ausgabe des obigen Codes:
-1 is smaller than 0 -1
Wenn ich einige Konzepte durcheinander gebracht habe, lassen Sie es mich bitte wissen. Danke ~
quelle
signed int c = a+b;
oben ruft UB auf. Der resultierende Typ von a + b ist ohne Vorzeichen und der berechnete Wert liegt außerhalb des Bereichs einer vorzeichenbehafteten Ganzzahl.int