2008-12-04 4 views
12

Wie erfolgt das Casting ohne Datenverlust im Compiler?Wie gehen C/C++ - Compiler mit Typumwandlung zwischen Typen mit unterschiedlichen Wertebereichen um?

Zum Beispiel:

int i = 10; 
UINT k = (UINT) k; 

float fl = 10.123; 
UINT ufl = (UINT) fl; // data loss here? 

char *p = "Stackoverflow Rocks"; 
unsigned char *up = (unsigned char *) p; 

Wie funktioniert der Compiler diese Art der Schublade gesteckt zu handhaben? Ein Low-Level-Beispiel, das die Bits zeigt, würde sehr geschätzt werden.

Antwort

18

Nun, beachten Sie zunächst, dass eine Besetzung eine explizite Anfrage ist, einen Wert eines Typs in einen Wert eines anderen Typs zu konvertieren. Ein Cast erzeugt auch immer ein neues Objekt, das vom Cast-Operator als temporäres Objekt zurückgegeben wird. Durch das Umwandeln in einen Referenztyp wird jedoch kein neues Objekt erstellt. Das Objekt, auf das der Wert verweist, wird als Referenz eines anderen Typs neu interpretiert.

Nun zu Ihrer Frage. Beachten Sie, dass es zwei Haupttypen von Umwandlungen:

  • Promotions: Diese Art gedacht werden kann von einem möglicherweise schmalen Typ einer breiteren Art von Gießen. Casting von Char zu Int, Short zu Int, Float zu Double sind alle Promotions.
  • Konvertierungen: Diese ermöglichen das Umwandeln von long in int, int in unsigned int und so weiter. Sie können grundsätzlich Informationsverlust verursachen. Es gibt Regeln dafür, was passiert, wenn Sie beispielsweise einem vorzeichenlosen typisierten Objekt eine -1 zuweisen. In einigen Fällen kann eine falsche Konvertierung zu einem nicht definierten Verhalten führen. Wenn Sie ein Double zuweisen, das größer ist als das, was ein Float in einem Float speichern kann, ist das Verhalten nicht definiert.

Schauen wir uns Ihre Abgüsse:

int i = 10; 
unsigned int k = (unsigned int) i; // :1 

float fl = 10.123; 
unsigned int ufl = (unsigned int) fl; // :2 

char *p = "Stackoverflow Rocks"; 
unsigned char *up = (unsigned char *) p; // :3 
  1. Diese Umwandlung eine Umwandlung zu geschehen verursacht. Es passiert kein Datenverlust, da 10 garantiert von einer unsigned int gespeichert wird. Wenn die ganze Zahl negativ wäre, würde der Wert im Grunde den maximalen Wert eines unsigned int umhüllen (siehe 4.7/2).
  2. Der Wert 10.123 ist auf 10 abgeschnitten. Hier ist es tut Ursache von Informationen verloren, offensichtlich. Wenn 10 in ein vorzeichenloses int passt, wird das Verhalten definiert.
  3. Dies erfordert tatsächlich mehr Aufmerksamkeit. Erstens gibt es eine veraltete Konvertierung von einem Zeichenfolgenliteral in char*. Aber lass uns das hier ignorieren. (siehe here). Noch wichtiger: Was passiert, wenn Sie auf einen unsignierten Typ umwandeln? Tatsächlich ist das Ergebnis davon nicht spezifiziert per 5.2.10/7 (beachte, dass die Semantik dieser Umwandlung dieselbe ist wie die Verwendung von reinterpret_cast in diesem Fall, da dies die einzige C++ - Umwandlung ist, die dies kann):

Ein Zeiger auf ein Objekt kann explizit in einen Zeiger auf ein Objekt anderen Typs konvertiert werden. Außer dass ein rvalue vom Typ "Zeiger auf T1" in den Typ "Zeiger auf T2" konvertiert wird (wobei T1 und T2 Objekttypen sind und die Ausrichtungsanforderungen von T2 nicht strenger sind als die von T1) und zurück zu seinem ursprünglichen Typ ergibt der ursprüngliche Zeigerwert, das Ergebnis einer solchen Zeigerumwandlung ist nicht spezifiziert.

So können Sie den Zeiger nur verwenden, nachdem Sie erneut auf char * zurückgeworfen haben.

+0

Hinweis: Dies gilt nur in C++ nicht in C. – Stargateur

5

"Typ" in C und C++ ist eine Eigenschaft, die Variablen zugewiesen wird, wenn sie im Compiler behandelt werden. Die Eigenschaft existiert zur Laufzeit nicht mehr, außer für virtuelle Funktionen/RTTI in C++.

Der Compiler verwendet den Typ der Variablen, um eine Menge Dinge zu bestimmen. Zum Beispiel, in der Zuweisung eines float zu einem int, wird es wissen, dass es konvertieren muss. Beide Typen sind wahrscheinlich 32 Bits, aber mit unterschiedlichen Bedeutungen. Es ist wahrscheinlich, dass die CPU eine Anweisung hat, aber ansonsten würde der Compiler eine Umwandlungsfunktion aufrufen. I.e. & __stack[4] = float_to_int_bits(& __stack[0])

Die Konvertierung von char * zu unsigned char * ist sogar einfacher. Das ist nur ein anderes Label. Auf Bit-Ebene sind p und up identisch. Der Compiler muss nur daran denken, dass * p eine Zeichenerweiterung benötigt, während * up dies nicht tut.

8

Die beiden C-Style-Modelle in Ihrem Beispiel sind verschiedene Arten von Cast. In C++, würden Sie normalerweise schreiben sie

unsigned int uf1 = static_cast<unsigned int>(fl); 

und

unsigned char* up = reinterpret_cast<unsigned char*>(p); 

Der erste führt eine arithmetische gegossen, die die Gleitkommazahl abschneidet, so dass es zu Datenverlust.

Die zweite ändert keine Daten - sie weist den Compiler nur an, den Zeiger als einen anderen Typ zu behandeln. Bei dieser Art von Besetzung ist Vorsicht geboten: Es kann sehr gefährlich sein.

1

Guss bedeutet unterschiedliche Dinge, je nachdem, was sie sind. Sie können nur Umbenennungen eines Datentyps sein, ohne dass sich die dargestellten Bits ändern (die meisten Umwandlungen zwischen ganzzahligen Typen und Zeigern sind wie diese) oder Konvertierungen, die nicht einmal die Länge beibehalten (z. B. zwischen double und int bei den meisten Compilern). . In vielen Fällen ist die Bedeutung einer Besetzung einfach nicht spezifiziert, was bedeutet, dass der Compiler etwas Vernünftiges tun muss, aber nicht genau dokumentieren muss.

Ein Cast muss nicht einmal einen brauchbaren Wert ergeben. Etwas wie char * cp; float * fp; cp = malloc(100); fp = (float *)(cp + 1); wird fast sicher zu einem falsch ausgerichteten Pointer führen, der das Programm auf einigen Systemen zum Absturz bringen wird, wenn das Programm versucht, es zu benutzen.