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 longund enum.
_Bool/ boolwird 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 intist 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.hsortieren auch hier mit dem gleichen Rang wie der Typ, dem sie auf dem gegebenen System entsprechen. Hat zum Beispiel int32_tden gleichen Rang wie intauf 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 intoder unsigned intverwendet werden kann:
- Ein Objekt oder Ausdruck mit einem ganzzahligen Typ (außer intoder unsigned int), dessen ganzzahliger Konvertierungsrang kleiner oder gleich dem Rang von intund ist unsigned int.
Was dieser etwas kryptische Text in der Praxis bedeutet, ist , dass _Bool, charund short(und auch int8_t, uint8_tusw.) 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 intden 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 intalle 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, intwenn 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 shortOperanden haben und intzufällig dieselbe Größe wie shortauf 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 shortunsigned intshortintint
Die harte Realität, die durch die ganzzahligen Beförderungen verursacht wird, bedeutet, dass fast keine Operation in C mit kleinen Typen wie charoder ausgeführt werden kann short. Operationen werden immer an intoder 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 charOperanden enthält, die Operanden heraufstufen intund 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 intund das Ergebnis von x - yist vom Typ int. Das heißt, wir bekommen -1stattdessen 255was 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("%ues 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 sizeofOperatoren 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 ifAnweisung 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 + bin 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 - aist unsigned intund bist signed int. Daher wird der Operator bvorü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 shortin Beispiel 3 das Problem behebt, liegt darin, dass shortes sich um einen kleinen Ganzzahltyp handelt. Dies bedeutet, dass beide Operanden eine Ganzzahl sind, intdie 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.
shortenger ist alsint(oder mit anderen Worten, es wird angenommen, dassintalle Werte von dargestellt werden könnenunsigned short).