Arithmetische Umwandlung
Unter arithmetischer Umwandlung wird ein in C und C++ eingebauter Mechanismus verstanden, der zwei Werte mit unterschiedlichem arithmetischem Typ (Integer- oder Fliesskomma-Typen) so umwandelt, dass sie danach denselben Typ und möglichst den ursprünglichen Wert haben. Dieser Mechanismus wird für Operatoren verwendet, welche zwei arithmetische Operanden erwarten.
Details
Operatoren erzeugen grundsätzlich je nach Typ der verknüpften Operanden unterschiedlichen Assembler-Code. Wenn ein Operator zwei Operanden erwartet, muss somit theoretisch für jede Typ-Kombination eine entsprechende Umsetzung in Assembler existieren. Gerade jedoch für Operatoren, welche arithmetische Typen wie Integer- oder Fliesskomma-Typen erwarten, existieren in Assembler beinahe ausschliesslich nur Befehle, welche Werte mit gleichem Typ miteinander verknüpfen können. Die arithmetische Umwandlung von C und C++ sorgt somit dafür, dass Werte unterschiedlichen Typs in einen bestmöglichen, gleichen Typ umgewandelt werden, worauf der Operator den entsprechenden Assemblerbefehl erzeugen kann.
Die arithmetische Umwandlung behandelt somit explizit die Wahl eines bestmöglichen Typs, um Operatoren, welche zwei arithmetische Operanden erwarten, eine Verknüpfung zu ermöglichen.
Die Wahl des Umwandlungstypen
Jeder arithmetische Typ besitzt eine sogenannte Mächtigkeit
, wobei long double
als der mächtigste Typ gilt. Als Faustregel gilt bei der arithmetischen Umwandlung: Der schwächere wird in den mächtigeren umgewandelt
. Je nach Compiler kann diese Regel jedoch für gewisse Fälle anders ausfallen. Diese Spezialfälle werden auf dieser Seite nicht beschrieben sondern auf andere Quellen verwiesen. Es ist jedoch speziell im Hinblick auf andere Quellen anzumerken, dass egal für welchen Compier stets zur Compilezeit bestimmt wird, in welchen Typ umgewandelt wird.
Die Art der Umwandlung wird in den Compilern durch die Abarbeitung von Regeln festgelegt, wobei die erste zutreffende Regel die bestimmende ist. Sie sind in dem nachfolgenden Text der Reihe nach von oben nach unten aufgelistet. Die erste Regel lautet:
- Haben beide Operanden denselben Typ, wird nichts umgewandelt.
Die Fliesskommatypen long double
, double
und float
sind mächtiger als alle Integer-Typen und long double
ist der mächtigste von allen. Die Regeln für Fliesskomma-Typen sind die folgenden:
- Ist ein Operand ein
long double
, so wird der andere ebenfalls in ein long double
konvertiert.
- Ist ein Operand ein
double
, so wird der andere ebenfalls in ein double
konvertiert.
- Ist ein Operand ein
float
, so wird der andere ebenfalls in ein float
konvertiert.
Ist keiner der Operanden ein Fliesskomma-Typ, so treten die Regeln der Integer-Typen in Kraft. Leider sind Integer-Typen historisch bedingt nicht immer konsistent, weswegen sich verschiedene Quellen von verschiedenen Compilern in diesem Punkt unterscheiden. Es ist somit nicht möglich, einheitliche Regeln anzugeben, allerdings bietet der Autor folgende einfache Regeln als Orientierung an, welche in heutigen Compilern zumindest in den nicht-extremen Fällen stimmen sollten:
- Ist ein Operand ein
unsigned long long
, so wird der andere ebenfalls in ein unsigned long long
konvertiert.
- Ist ein Operand ein
signed long long
, so wird der andere ebenfalls in ein signed long long
konvertiert.
- Ist ein Operand ein
unsigned int
, so wird der andere ebenfalls in ein unsigned int
konvertiert.
- Ist ein Operand ein
signed int
, so wird der andere ebenfalls in ein signed int
konvertiert.
Es ist davon auszugehen, dass auch heute noch verschiedene Standards in Benutzung sind, welche geringfügig unterschiedliche Mechanismen für die Umwandlung verwenden. Doch die oben genannten Regeln sollten (vielleicht abgesehen von gewissen Grenzfällen) die Grundprinzipien der arithmetischen Integer-Umwandlungen abdecken. Im Zweifelsfalle empfielt es sich, explizite Casts zu verwenden.
Wenn bislang immer noch keine Regel anwendbar wurde, so treten folgende Regeln in Kraft:
- Ist ein Operand ein
bool
, signed char
, signed short
oder w_char
, so werden BEIDE Operanden in einen signed int
konvertiert.
- Ist ein Operand ein
unsigned char
oder unsigned short
, so werden BEIDE Operanden in einen unsigned int
konvertiert.
Promotionen
Diese letzten beiden Regeln gehören zu den sogenannten Typ-Promotionen
. Eine Umwandlung wird dann als Promotion bezeichnet, wenn der Umwandlungs-Typ den Werteumfang des umzuwandelnden Typen vollständig und verlustfrei beschreiben kann. Die angegebenen Typen werden alle in einen int
umgewandelt, welcher als die effizienteste Integer-Recheneinheit eines Prozessors gilt. Ein Compiler nimmt solche Typ-Promotionen grundsätzlich automatisch vor, es könnte jedoch je nach Compiler auch ein dazu passender Assembler-Befehl generiert werden, wenn er denn existiert.
Ebenfalls als Promotion wird die Umwandlung von float
in double
bezeichnet. Der Typ double
gilt als der effizienteste Fliesskomma-Typ, welcher verlustfrei den gesamten Werteumfang eines float
darzustellen vermag. Da jedoch diese Umwandlung oftmals mehr Zeit benötigt als die Ausführung des schlussendlichen Operanden, sollte diese Promotion wenn immer möglich vermieden werden. Hierfür sollte auf die Kennzeichnung von floats als feste Werte im Quellcode geachtet, sowie explizite Casts verwendet werden.
Promotionen spielen eine untergeordnete Rolle in der alltäglichen Programmierung. Da sie verlustfreie Umwandlungen garantieren, führen sie normalerweise auch nicht zu Fehlern. Wenn jedoch der Compiler selbst über den Typ im Unklaren ist, ist er gezwungen, ebendiese Promotionen vorzunehmen, was insbesondere im Zusammenhang mit Parametern von variadischen Funktionen zu Problemen führen kann.
Weiteres
Die tatsächliche Umwandlung der Typen ineinander kann bei den entsprechenden Typen nachgelesen werden.
Der durch die oben genannten Regeln gefundene Umwandlungstyp deklariert bei vielen Operatoren gleichzeitig den Typ des Rückgabewertes des Operators, was bei den detailierten Beschreibungen der Operatoren jeweils angemerkt wird.
Die arithmetische Umwandlung erfolgt grundsätzlich automatisch, wann immer der Compiler dies als notwendig erachtet. In den allermeisten Fällen verläuft die automatische Umwandlung der Werte problemlos, allerdings gibt es Fälle, bei denen eingegriffen werden muss. Umwandlungen können somit durch die Angabe von Casts auch explizit erzwungen werden.
Das Problem einer solchen Umwandlung ist, dass verschiedene Typen generell nicht verlustfrei ineinander umgewandelt werden können. Beispielsweise ist es nicht möglich, eine Zahl mit Nachkommastellen (Fliesskommazahl) durch eine Zahl ohne Nachkommastellen (Integer) darzustellen. Es ist auch nicht möglich, eine negative Zahl mittels eines vorzeichenlosen Typs zu speichern. Und eine Zahl, die den Werteumfang eines Typs sprengt, kann ebenfalls nicht korrekt umgewandelt werden.
Eine denkbare Lösung wäre, alle Werte in einen möglichst mächtigen Typ umzuwandeln, mit dem möglichst alle Werte dargestellt werden können, wie beispielsweise long double
. Allerdings würde dies sowohl den Platzverbrauch, als auch die Laufzeit eines jeden Programmes um ein Vielfaches in die Höhe treiben und ausserdem sind Fliesskommazahlen für viele Informatikprobleme ungeeignet.
Bei der Verwendung von festen Werten (Literalen) mit arithmetischen Operatoren empfielt es sich aus verschiedenen Gründen, darauf zu achten, dass keine implizite arithmetische Umwandlungen auftreten. Um beispielsweise die Zahl 1
als Fliesskommazahl festzulegen, sollte 1.
geschreiben werden.
Es ist zu beachten, dass arithmetische Umwandlungen nicht gratis sind, sondern eine gewisse Anzahl an Assembleranweisungen kosten. Insbesondere Konvertierungen von Fliesskommazahlen erfordern komplexe Befehle auf Fliesskomma-Registern. Für zeitkritische Anwendungen lohnt es sich daher, auf diese Umwandlungen zu achten.