Der Typ »void *« in C
Eine Adresse enthält normalerweise zwei Informationen: Die Position und die Größe ihres Referenten. Solch eine normale Adresse nennen wir auch eine Objekt-Adresse.
Eine void-Adresse ist eine Adresse, der die zweite Information fehlt. Es handelt sich nur noch um eine Speicherposition, die aber keine Information über die Größe des Objekts an dieser Stelle mehr enthält.
Konvertierungen
Eine void-Adresse darf in eine Objektadresse gewandelt werden.
Eine Objekt-Adresse darf in eine void-Adresse gewandelt werden.
Wenn eine Objekt-Adresse in eine void-Adresse gewandelt wird und das Ergebnis dann wieder zurück in den ursprünglichen Objekt-Adreß-Typ gewandelt wird, dann ergibt sich wieder die ursprüngliche Objekt-Adresse.
Hin- und Herwandlung
Wenn eine Objektadresse in eine void-Adresse gewandelt wurde, kann sie wieder in den ursprünglichen Adreßtyp zurückgewandelt werden, was dann ein Adresse ergibt, welche deren Vergleich mit der ursprünglichen Objektadresse »[1]« ergibt.
main.c
#include <stdio.h>
int main( void )
{ int i = 27;
int * p = &i;
void * v = p;
int * r = v;
printf( "%d\n", p == r ); }- Protokoll
1
Bei einer void-Adresse »v« kann »sizeof *v« beispielsweise nicht ausgewertet werden.
main.c
#include <stdio.h>
int main( void )
{ int i = 27;
int * p = &i;
void * v = p;
printf( "%zu\n", sizeof *vp ); }- Protokoll
error: invalid application of 'sizeof' to a void type
Eine void-Adresse in einem ternären Ausdruck
Wenn der Typ eines Zweiges eines ternären Ausdrucks eine void-Adresse und der Typ des anderen Zweiges eine Objektadresse ist, dann hat der gesamte ternäre Ausdruck den Typ »void *«
main.c
#include <stdio.h>
int main( void )
{ int i = 27;
int * p = &i;
void * v = p;
printf( "%zu\n", sizeof( *( 1 ? p : v ))); }- Protokoll
error: invalid application of 'sizeof' to a void type
Vergleich mit einer Objektadresse
Wenn eine Objektadresse mit einer void-Adresse verglichen wird, wird sie zuvor implizit in eine void-Adresse gewandelt.
Unten erfolgt der Vergleich »q == p« also, als hätte man geschrieben »( void * )q == p«.
main.c
#include <stdio.h>
int main( void )
{ int i = 27;
int * p = &i;
void * v = p;
printf( "%d\n", v == p ); }- Protokoll
1
Dereferenzierung einer void-Adresse
Bei Verwendung einer void-Adresse »p« wird kein Objekt referenziert, weswegen die Dereferenzierung »*p« einer solchen Adresse auch kein Objekt ergibt, und es gibt nur wenig, was man mit solch einem void-Ausdruck tun kann. Im folgenden Programm wird der Wert des Ausdrucks »*q« einmal verworfen, und dann wird die Adresse »&*p« mit »p« verglichen (was »[1]« ergeben muß).
main.c
#include <stdio.h>
int main( void )
{ int i = 27;
int * p = &i;
void * v = p;
*v;
printf( "%d\n", &*v == p ); }- Protokoll
1
»%p« — Ausgabe von Adressen
Eine void-Adresse kann mit dem Formatierungsbuchstaben »p« (“pointer ”) ausgegeben werden. Adressen eines anderen Typs dürfen nicht auf diese Weise ausgegeben werden, sondern müssen erst in eine void-Adresse gewandelt werden. Jedoch darf der void-Zeiger mit »const« modifiziert worden sein.
main.c
#include <stdio.h>
int main( void )
{ int i = 27;
int * p = &i;
void * v = p;
printf( "%p\n", v ); }- Protokoll
000000000022fe4c
- Protokoll (ältere C -Implementation)
0022feb4
Die genaue Art und Weise, wie ein Zeiger als Text dargestellt wird ist implementationsdefiniert.
Ausgabe von Adressen von Variablen von Inkarnationen
Wir können nun die unterschiedlichen Adressen der Variablen (Parameter) unterschiedlicher Inkarnationen einer Funktion ausgeben.
main.c
#include <stdio.h>
void f( int const i )
{ if( i < 4 )
{ void const * const v = &i;
printf( "%d, %p\n", i, v );
f( i + 1 );
printf( "%d, %p\n", i, v ); }}int main( void ){ f( 0 ); }
- Protokoll
0, 000000000022fe30
1, 000000000022fe00
2, 000000000022fdd0
3, 000000000022fda0
3, 000000000022fda0
2, 000000000022fdd0
1, 000000000022fe00
0, 000000000022fe30
Undefininiertes Verhalten
Das Auslesen einer nicht-initialisierten lokalen Variablen hat undefiniertes Verhalten. In bestimmten Fällen kann der Wert eine Variablen einer anderen Funktion ausgelesen werden, die sich an derselben Adresse befindet. Eventuell können interne Details einer anderen Funktion auf diese Weise ausspioniert werden.
main.c
#include <stdio.h>
void f() { int i; printf( "f %p, %2d\n",( void * )&i, i ); i = 24; }
void g() { int i; printf( "g %p, %2d\n",( void * )&i, i ); i = 64; }
void h() { int i; printf( "h %p, %2d\n",( void * )&i, i ); i = 12; }
int main(){ f(); g(); h(); }
- Protokoll
f 000000000022fe4c, 0
g 000000000022fe4c, 0
h 000000000022fe4c, 0
Nulladreßkonstanten
Wir hatten schon »0« als Nulladreßkonstante kennengelernt. Auch »(( void * )0 )« darf als Nulladreßkonstante verwendet werden.