Adreßtypwandlungen in C
Wenn »E « ein Ausdruck und »T « ein Typ ist, dann ist »( T )E « ein Ausdruck mit dem Typ »T «. Wir nennen solch einen Ausdruck »( T )E « auch einen cast -Ausdruck, und »( T )« einen cast -Operator oder cast.
- Aussprachehinweis
- cast kæst (n) (⌊ˈkæ̣sτ⌋)
Wir interessieren uns hier besonders für die Fälle, in denen »T « ein Adreßtyp und »E« ein Ausdruck mit einem Adreßtyp ist.
Kompatibilität von Typen
Zwei Typen werden als kompatibel angesehen, wenn sie praktisch gleich sind. Daneben gibt es noch einige Umstände unter denen zwei verschiedene Typen als kompatibel gelten, aber diese sind fast alle so eng gefaßt, daß es im allgemeinen reicht, sich unter zwei kompatiblen Typen zwei gleiche Typen vorzustellen.
Effektive Typen von Objekten
Der effektive Typ eines Objektes ist der Typ, mit dem es deklariert wurde.
Nach der Deklaration »int c;« hat das Objekt der Variablen »c« beispielsweise den Typ »int«.
(Wir reden hier von dem „effektiven Typ eines Objektes“, weil Typen zunächst zu Ausdrücken (Variablen) und nicht zu Objekten gehört. Außerdem werden wir später noch Objekte ohne Deklaration kennenlernen werden, die aber trotzdem einen effektiven Typ haben können.)
Zugriffe auf Objekte
Ein Zugriff auf ein Objekt ist nur mit einem Ausdruck gestattet, der einen Typ hat, welcher zum effektiven Typ des Objekts kompatibel ist oder der in einer der weiter unten behandelten Beziehungen zum Typ des Objektes steht.
Daher ist das folgende Programm nicht gestattet.
main.c
#include <stdio.h>
int main( void )
{ int n = 123;
double * p =( double * )&n;
printf( "%g\n", *p ); }Protokoll
1.6976e-313
Der Typ von »*p« ist »double«, aber der effektive Typ des Objekts »n« ist »int«.
Das folgende Programm ist hingegen gestattet, da der Typ von »*p« gleich dem effektiven Typ des Objekts »n« ist, also gleich »int«, ist.
main.c
#include <stdio.h>
int main( void )
{ int n = 123;
int * p =( int * )&n;
printf( "%d\n", *p ); }- Protokoll
123
Qualifizierte Zugriffe
Hinzufügen von »const«
Zusätzlich zum vorgenannten Fall, ist es auch gestattet, daß sich der zum Zugriff verwendete Typ durch die An- oder Abwesenheit einer Qualifikation wie »const« vom effektiven Typ des Objektes unterscheidet.
main.c
#include <stdio.h>
int main( void )
{ int const n = 123;
printf( "%d\n", *( ( int const * const )&n )); }Protokoll
123
(»int *« ist kein Typ, der sich durch die An- oder Abwesenheit einer Qualifikation von »int const *« unterscheidet, da es hierbei nur um Qualifikationen geht, welche ein Objekt jenes Typs direkt betreffen.)
Entfernen von »const«
Es ist auch gestattet, ein »const« zu entfernen, doch wird dies nicht empfohlen.
main.c
#include <stdio.h>
int main( void )
{ int const n = 123;
printf( "%d\n", *( ( int const * )( int const * const )&n )); }Protokoll
123
Es ist aber nicht gestattet, ein konstantes Objekt zu modifizieren, auch wenn dies mit einem Typ erfolgt, der nicht const-modifiziert ist.
- C 2017 6.7.3p6 (Zitat)
- If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.
main.c
#include <stdio.h>
int main( void )
{ int const n = 123;
int * p =( int * )&n;
*p = 456; /* undefined behavior */
printf( "%d\n", n ); }Protokoll
456
In der Praxis kommt es manchmal vor, daß ein »const« durch eine Typwandlung entfernt wird, wenn ein Objekt verändert werden soll, von dem bekannt ist, daß es nicht konstant ist. Jedoch ist solch eine Typwandlung eigentlich nur ein Symptom für einen Fehler an einer anderen Stelle, der erst dazu führt, daß solch eine Wandlung nötig wird. Im besten Falle sollten solche Wandlungen nie nötig sein.
Zugriffe mit veränderter Vorzeichenregelung
Es ist erlaubt, auf ein Objekt eines numerischen Typs mit einer Adresse mit einer anderen Vorzeichenregelung zuzugreifen. In diesem Falle wird die interne Darstellung des Objektes mit der anderen Vorzeichenregelung interpretiert. Das Verhalten ist dann zwar nicht undefiniert, aber implementationsspezifisch, also nicht portabel.
main.c
#include <stdio.h>
#include <limits.h>int main( void )
{ unsigned u = UINT_MAX;
signed * sp =( signed * )&u;
printf( "%d\n", *sp ); }- Protokoll
-1
Solche Zugriffe mit veränderter Vorzeichenregelung sind in der Praxis eher selten.
Zugriffe mit char-Typen
Schließlich ist der Zugriff auf ein Objekt mit einer Adresse eines char-Typs gestattet. Dies erlaubt es, die interne Darstellung eines Objektes Byte für Byte „auszulesen“ (ein char-Objekt repräsentiert ein Byte). Wir zeigen hier zunächst den Zugriff auf das Byte, das sich an derselben Stelle wie ein int-Objekt befindet.
main.c
#include <stdio.h>
int main( void )
{ int n = 10000;
printf( "%zu\n", sizeof( n ));
unsigned char * cp =( unsigned char * )&n;
printf( "%d\n", *cp ); }- Protokoll
4
16
Das int-Objekt »n« umfaßt insgesamt vier Byte, und das erste davon hat den Wert »[16]«. Der Zugriff auf die anderen drei Bytes wird später gezeigt werden.
Zitat *
- C 2018 6.5p7 (Zitat) (vereinfacht)
- 6.5 Expressions
- …
- 7 An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- — a type compatible with the effective type of the object,
- — a qualified version of a type compatible with the effective type of the object,
- — a type that is the signed or unsigned type corresponding to the effective type of the object,
- — a type that is the signed or unsigned type corresponding to a qualified version of the effective type of the object,
- — an aggregate or union type that includes one of the aforementioned types among its members (including, recursively, a member of a subaggregate or contained union), or
- — a character type.