Castings

Castings dienen dazu, die Bedeutung von Daten anders zu interpretieren, bzw. der Programmiersprache zu sagen, dass man sich bewusst ist, was man da eigentlich tut.

Als Beispiel:

char c = 4;
int i;
 
i = c;        // kein Problem: denn char (8 Bit) ist vollständig 
              //               in int (32 Bit) speicherbar;
c = i;        // Problem:      der Computer kann die 32 Bit des ints nicht
              //               in den 8 Bit des chars speichern: 
              //               Es könnten Daten verloren gehen.

Ein C-Casting löst dieses Problem wie folgt:

c = (char) i; // Problem gelöst.
              // Ich behaupte, ich weiß, was ich da tue und deswegen soll der Compiler
              // das nun ohne zu meckern durchführen.

Also wird der Compiler nun die niedrigsten 8 Bit des Integers in die Character-Variable kopieren. Sollte die Zahl größer sein, so gehen diese Informationen verloren. Dass das nicht passiert - oder dass das gewollt ist, dafür übernimmt der Entwickler durch das Casting die Verantwortung.

Vorsicht beim Casten

Mit diesem Trick lassen sich Zeiger in beliebige andere Zeigertypen konvertieren.

Stellen wir uns vor, wir haben eine Subnetzmaske in einer Integervariablen gespeichert:

#include <stdio.h>
#include <stdlib.h>
 
void func( int ip )
{
  unsigned char * elements;
 
  elements = (unsigned char *) &ip; // Hier behauptet der Programmierer, dass &ip
                                    // ein Zeiger auf eine vorzeichenlose 
                                    // Charactervariable, bzw. -Array, sei.
 
  printf( "Subnetz : %d.%d.%d.%d\n",  
          elements[0], 
          elements[1], 
          elements[2], 
          elements[3]  );
}
 
int main( void )
{
  int ip = 0x0101A8C0;              // (big endian - Intel-CPUs)
 
  func( ip );
 
  return EXIT_SUCCESS;
}

Dieses Programm liest die IP-Adresse als vier 8-Bit-Zahlen aus einer 32-Bit Integer. Die 32 Bit Zahl wird also durch das Casting anders interpretiert, obwohl es die selben Daten sind - die Daten dafür also nichtmal kopiert werden müssen. Das geht schneller, als die einzelnen Werte durch Bitschieben aus dem Integer herauszuarbeiten.

Dieses Beispiel ließe sich schöner mit einem union IPAdress lösen. In der Praxis fehlen solche Unions jedoch häufig, so dass man derartige Übersetzungen selbst formulieren muss.

Vorsicht beim Casten

Leider ist Casten alles andere als sicher. Gehen wir diesmal den umgekehrten Weg

#include <stdio.h>
#include <stdlib.h>
 
void func( void )
{
  char c = 1;
  int * pi;
 
  pi = (int *) &c; // Hier behauptet der Programmierer, dass &c ein Zeiger auf
                   // eine Integervariable sei.
 
  printf( " c : %d\n",  c  );
  printf( "*pi: %d\n", *pi );
  *pi = 1;
  printf( "*pi: %d\n", *pi );
}
 
int main( void )
{
  printf( "Start\n" );
  func();
  printf( "Ende\n" );
 
  return EXIT_SUCCESS;
}

Hier zeigt nun der Zeiger pi auf die Adresse der Variablen c, welche 1 Byte groß ist. Wird nun mit dem Zeiger auf die Int-Variable zugegriffen, so werden 4 Byte ausgelesen, obwohl uns eigentlich nur erlaubt ist das eine Byte der Variablen c zu benutzen.

Nun geben wir c aus, also 1 Byte und das ist entsprechend der Initialisierung 1. Greifen wir auf den Zeiger pi zu, dann lesen wir 4 Byte aus, von denen uns drei nicht gehören, wir also auch nicht wissen, was da drin steht. Weisen wir nun diesen 4 Bytes, von denen uns drei nicht gehören, etwas zu - hier die 1 - dann überschreiben wir drei Bytes, die der Computer später vielleicht für etwas anderes benötigt.

Nach der Zuweisung lesen wir die vier Bytes wieder aus und erhalten, wie erwartet die Zahl, die wir zugewiesen haben. Leider stürzt das Programm schlussendlich ab:

Ambassador:proggenOrg xin$ ./a.out 
Start
 c : 1
*pi: -1074077695
*pi: 1
Ende
Segmentation fault
Ambassador:proggenOrg xin$

Fazit

Casten ist eine Technik, die man sehr vorsichtig anwenden muss, da man dem Compiler sagt, wie er die Daten nun zu interpretieren hat. Entsprechend kann er keine Hilfestellung mehr geben, wenn man selbst etwas beschreibt, was man aus den Daten nicht lesen kann - wie hier aus einem Zeiger auf ein Char einen Zeiger auf ein Int zu machen. Das würde bei einem Zeiger auf ein Char-Array mit mindestens 4 Bytes soweit funktionieren, aber das kann der Compiler nicht mehr prüfen, wenn der Entwickler vorher durch das Casting festgelegt hat, dass er das genau so haben will, wie er es formuliert hat.

Ein Casting verbietet dem Compiler gewissermaßen den Mund, um uns hilfreiche Tipps zu geben. Castings sind also so selten wie nur irgendwie möglich einzusetzen und wenn man gezwungen ist, sie einzusetzen, dann mit äußerster Vorsicht.

Hinweis für C++-Programmierer

Wer C++ programmiert, sollte diese Form von Castings nicht mehr verwenden. C++-Casts bieten hier eine genauere Beschreibung, was man eigentlich erreichen möchte: static_cast, const_cast, dynamic_cast, reinterpret_cast und das Umformen durch Neukonstruieren.