Kapitel 4. Typ-Konvertierungen
Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com
In C können Operanden verschiedener Typen in einer Operation kombiniert werden. Zum Beispiel sind die folgenden Ausdrücke zulässig:
double
dVar
=
2.5
;
// Define dVar as a variable of type double.
dVar
*=
3
;
// Multiply dVar by an integer constant.
if
(
dVar
<
10L
)
// Compare dVar with a long-integer constant.
{
/* ... */
}
Wenn die Operanden unterschiedliche Typen haben, versucht der Compiler, sie in einen einheitlichen Typ zu konvertieren, bevor er die Operation ausführt. In bestimmten Fällen musst du außerdem Anweisungen zur Typumwandlung in dein Programm einfügen. Eine Typumwandlung liefert den Wert eines Ausdrucks in einem neuen Typ, der entweder der Typ void
(was bedeutet, dass der Wert des Ausdrucks verworfen wird; siehe "Ausdrücke vom Typ void") oder ein skalarer Typ - also ein arithmetischer Typ oder ein Zeiger - sein kann. Ein Zeiger auf eine Struktur kann zum Beispiel in einen anderen Zeigertyp umgewandelt werden. Ein tatsächlicher Strukturwert kann jedoch nicht in einen anderen Strukturtyp umgewandelt werden.
Der Compiler führt implizite Typumwandlungen durch, wenn die Typen der Operanden nicht übereinstimmen oder wenn du eine Funktion mit einem Argument aufrufst, dessen Typ nicht mit dem entsprechenden Parameter der Funktion übereinstimmt. Programme führen auch implizite Typumwandlungen durch, wenn sie Variablen initialisieren oder ihnen auf andere Weise Werte zuweisen. Wenn die notwendige Umwandlung nicht möglich ist, gibt der Compiler eine Fehlermeldung aus.
Du kannst Werte auch explizit mit dem Cast-Operator von einem Typ in einen anderen umwandeln (siehe Kapitel 5):
(type_name) expression
Im folgenden Beispiel bewirkt der Cast-Operator, dass die Division einer Ganzzahlvariablen durch eine andere als Fließkommaoperation durchgeführt wird:
int
sum
=
22
,
count
=
5
;
double
mean
=
(
double
)
sum
/
count
;
Da der Cast-Operator Vorrang vor der Division hat, wird der Wert von sum
in diesem Beispiel zunächst in den Typ double
umgewandelt. Der Compiler muss dann implizit den Divisor, den Wert von count
, in denselben Typ umwandeln, bevor er die Division durchführt.
Du solltest den Cast-Operator immer dann verwenden, wenn die Möglichkeit besteht, dass Informationen verloren gehen, wie zum Beispiel bei einer Konvertierung von int
nach unsigned int
. Explizite Casts vermeiden Compiler-Warnungen und zeigen anderen Programmierern die Typumwandlungen in deinem Programm an. Wenn du z. B. den Rückgabewert einer Funktion mit einem expliziten Cast nach void
verwirfst, weist du darauf hin, dass du die Fehlerhinweise der Funktion möglicherweise ignorierst.
Zur Veranschaulichung der impliziten Typkonvertierungen, die der Compiler bereitstellt, wird der Cast-Operator in den Beispielen in diesem Kapitel jedoch nur dann verwendet, wenn es unbedingt notwendig ist.
Umwandlung von arithmetischen Typen
Typ Konvertierungen sind immer zwischen zwei beliebigen arithmetischen Typen möglich und der Compiler führt sie implizit durch, wo immer es nötig ist. Bei der Umwandlung bleibt der Wert eines Ausdrucks erhalten, wenn der neue Typ ihn darstellen kann. Das ist nicht immer der Fall. Wenn du zum Beispiel einen negativen Wert in einen vorzeichenlosen Typ umwandelst oder einen Fließkommabruch vom Typ double
in den Typ int
umwandelst, kann der neue Typ den ursprünglichen Wert einfach nicht darstellen. In solchen Fällen gibt der Compiler in der Regel eine Warnung aus.
Hierarchie der Typen
Wenn arithmetische Operanden unterschiedliche Typen haben, wird die implizite Typumwandlung durch den Rang der Typen bestimmt. Die Typen werden nach den folgenden Regeln geordnet:
-
Zwei vorzeichenlose Ganzzahltypen haben unterschiedliche Umwandlungsränge. Wenn einer breiter ist als der andere, hat er einen höheren Rang.
-
Jeder vorzeichenbehaftete Ganzzahlentyp hat denselben Rang wie der entsprechende vorzeichenlose Typ. Der Typ
char
hat den gleichen Rang wiesigned char
undunsigned char
. -
Die Standard-Ganzzahltypen sind in der Reihenfolge geordnet:
_Bool < char < short < int < long < long long
-
Jeder Standard-Ganzzahltyp hat einen höheren Rang als ein erweiterter Ganzzahltyp derselben Breite (erweiterte Ganzzahltypen werden in "Ganzzahltypen mit exakter Breite (C99)" beschrieben ).
-
Jeder Aufzählungstyp hat denselben Rang wie sein entsprechender Ganzzahlentyp (siehe "Aufzählungstypen").
-
Die Gleitkommatypen sind in der folgenden Reihenfolge angeordnet:
float < double < long double
-
Der Fließkommatyp mit dem niedrigsten Rang,
float
, hat einen höheren Rang als jeder Ganzzahltyp. -
Jeder komplexe Gleitkommatyp hat den gleichen Rang wie der Typ seiner realen und imaginären Teile.
Ganzzahlige Förderung
In jedem Ausdruck kannst du anstelle eines Operanden vom Typ int
oder unsigned int
immer einen Wert verwenden, dessen Typ niedriger ist als int
. Du kannst auch ein Bit-Feld als Ganzzahl-Operand verwenden (Bit-Felder werden in Kapitel 10 behandelt). In diesen Fällen wendet der Compiler die Integer-Promotion an: Jeder Operand, dessen Typ niedriger als int
ist, wird automatisch in den Typ int
umgewandelt, sofern int
alle Werte des ursprünglichen Typs des Operanden darstellen kann. Wenn int
nicht ausreicht, wird der Operand in unsigned int
umgewandelt.
Bei der Integer-Promotion bleibt der Wert des Operanden immer erhalten. Hier sind einige Beispiele:
char
c
=
'?'
;
unsigned
short
var
=
100
;
if
(
c
<
'A'
)
// The character constant 'A' has type int:
// the value of c is implicitly promoted
// to int for the comparison.
var
=
var
+
1
;
// Before the addition, the value of var
// is promoted to int or unsigned int.
In der letzten dieser Anweisungen erhöht der Compiler den ersten Summanden, den Wert von var
, in den Typ int
oder unsigned int
, bevor er die Addition durchführt. Wenn int
und short
die gleiche Breite haben, was auf einem 16-Bit-Computer wahrscheinlich ist, dann ist der vorzeichenbehaftete Typ int
nicht breit genug, um alle möglichen Werte der Variable unsigned short
var
darzustellen. In diesem Fall wird der Wert von var
auf unsigned int
übertragen. Nach der Addition wird das Ergebnis in unsigned short
umgewandelt und var
zugewiesen.
Übliche arithmetische Umrechnungen
Die üblichen arithmetischen Umrechnungen sind die impliziten Umrechnungen, die bei den meisten Operatoren automatisch auf Operanden verschiedener arithmetischer Typen angewendet werden. Der Zweck der üblichen arithmetischen Umwandlungen ist es, einen gemeinsamen reellen Typ für alle Operanden und das Ergebnis der Operation zu finden.
Für die folgenden Operatoren werden die üblichen arithmetischen Umrechnungen implizit durchgeführt:
- Arithmetische Operatoren mit zwei Operanden
-
*
,/
,%
,+
, und-
- Relationen und Gleichheitsoperatoren
-
<
,<=
,>
,>=
,==
, und!=
- Die bitweisen Operatoren
-
&
,|
, und^
- Der ternäre Operator
-
?:
(für den zweiten und dritten Operanden)
Mit Ausnahme der Vergleichsoperatoren und der Gleichheitsoperatoren ist der gemeinsame reelle Typ, den man durch die üblichen arithmetischen Umwandlungen erhält, in der Regel der Typ des Ergebnisses. Wenn jedoch einer oder mehrere der Operanden einen komplexen Fließkommatyp haben, hat auch das Ergebnis einen komplexen Fließkommatyp.
Die üblichen arithmetischen Umrechnungen werden wie folgt durchgeführt:
-
Wenn einer der beiden Operanden einen Fließkommatyp hat, wird der Operand mit dem niedrigeren Umwandlungsrang in einen Typ mit demselben Rang wie der andere Operand umgewandelt. Reelle Typen werden jedoch nur in reelle Typen umgewandelt und komplexe Typen nur in komplexe.
Mit anderen Worten: Wenn einer der beiden Operanden einen komplexen Gleitkommatyp hat, passt die übliche arithmetische Umwandlung nur zu dem realen Typ, auf dem der tatsächliche Typ des Operanden basiert. Hier sind einige Beispiele:
#include <complex.h>
// ...
short
n
=
-
10
;
double
x
=
0.5
,
y
=
0.0
;
float
_Complex
f_z
=
2.0F
+
3.0F
*
I
;
double
_Complex
d_z
=
0.0
;
y
=
n
*
x
;
// The value of n is converted to type double.
d_z
=
f_z
+
x
;
// Only the value of f_z is converted to
// double _Complex.
// The result of the operation also has
// type double _Complex.
f_z
=
f_z
/
3
;
// The constant value 3 is converted to float.
d_z
=
d_z
−
f_z
;
// The value of f_z is converted to
// the type double _Complex.
-
Wenn beide Operanden Ganzzahlen sind, wird zunächst eine Integer-Promotion für beide Operanden durchgeführt. Wenn die Operanden nach der Integer-Promotion immer noch unterschiedliche Typen haben, wird die Umwandlung wie folgt fortgesetzt:
-
Wenn ein Operand einen Typ ohne Vorzeichen hat
T
hat, dessen Umwandlungsrang mindestens so hoch ist wie der des anderen Operanden, dann wird der andere Operand in den TypT
. -
Andernfalls hat ein Operand einen vorzeichenbehafteten Typ
T
dessen Umwandlungsrang höher ist als der des anderen Operandentyps. Der andere Operand wird nur dann in den TypT
umgewandelt, wenn der TypT
in der Lage ist, alle Werte seines vorherigen Typs darzustellen. Wenn nicht, werden beide Operanden in den vorzeichenlosen Typ umgewandelt, der dem vorzeichenbehafteten Typ entsprichtT
.
-
Die folgenden Codezeilen enthalten einige Beispiele:
int
i
=
-
1
;
unsigned
int
limit
=
200U
;
long
n
=
30L
;
if
(
i
<
limit
)
x
=
limit
*
n
;
In diesem Beispiel muss, um den Vergleich in der Bedingung if
auszuwerten, der Wert von i
, -1, zunächst in den Typ unsigned int
umgewandelt werden. Das Ergebnis ist eine große positive Zahl. Auf einem 32-Bit-System ist diese Zahl232 - 1, und auf jedem System ist sie größer als limit
. Daher ist die Bedingung if
falsch.
In der letzten Zeile des Beispiels wird der Wert von limit
in den Typ von n
, long
, umgewandelt, wenn der Wertebereich von long
den gesamten Wertebereich von unsigned int
enthält. Wenn nicht - zum Beispiel, wenn sowohl int
als auch long
32 Bit breit sind - werden beide Multiplikatoren in unsigned long
umgewandelt.
Bei den üblichen arithmetischen Umrechnungen bleibt der Wert des Operanden erhalten, außer in den folgenden Fällen:
-
Wenn eine große Ganzzahl in einen Fließkommatyp umgewandelt wird, kann es sein, dass die Genauigkeit des Zieltyps nicht ausreicht, um die Zahl genau darzustellen.
-
Negative Werte liegen außerhalb des Wertebereichs von Typen ohne Vorzeichen.
In diesen beiden Fällen werden Werte, die den Bereich oder die Genauigkeit des Zieltyps überschreiten, wie in "Die Ergebnisse der arithmetischen Typumwandlung" beschrieben umgewandelt .
Andere implizite Typkonvertierungen
Der Compiler konvertiert auch in den folgenden Fällen automatisch arithmetische Werte:
-
Bei Zuweisungen und Initialisierungen wird der Wert des rechten Operanden immer in den Typ des linken Operanden umgewandelt.
-
Bei Funktionsaufrufen werden die Argumente in die Typen der entsprechenden Parameter umgewandelt. Wenn die Parameter nicht deklariert wurden, werden die Standard-Argument-Promotionen angewendet: Ganzzahlige Argumente werden in Ganzzahl-Argumente umgewandelt, und Argumente vom Typ
float
werden indouble
umgewandelt. -
In
return
Anweisungen wird der Wert desreturn
Ausdrucks in den Rückgabetyp der Funktion umgewandelt.
Bei einer zusammengesetzten Zuweisung, wie z. B. x += 2.5
, werden die Werte beider Operanden zunächst den üblichen arithmetischen Umrechnungen unterzogen, dann wird das Ergebnis der arithmetischen Operation wie bei einer einfachen Zuweisung in den Typ des linken Operanden umgewandelt. Die meisten Compiler geben eine Warnung aus, wenn der Typ des linken Operanden nicht in der Lage ist, den Wert des rechten Operanden darzustellen. Hier sind einige Beispiele:
#include <math.h>
// Declares the function double sqrt( double ).
int
i
=
7
;
float
x
=
0.5
;
// The constant value is converted from double to float.
i
=
x
;
// The value of x is converted from float to int.
x
+=
2.5
;
// Before the addition, the value of x is converted to
// double. Afterward, the sum is converted to float for
// assignment to x.
x
=
sqrt
(
i
);
// Calculate the square root of i:
// The argument is converted from int to double;
// the return value is converted from double to
// float for assignment to x.
long
my_func
()
{
/* ... */
return
0
;
// The constant 0 is converted to long, the function's
// return type.
}
Die Ergebnisse der arithmetischen Typkonvertierung
Da die verschiedenen Typen unterschiedliche Zwecke, Darstellungsmerkmale und Beschränkungen haben, erfordert die Umwandlung eines Wertes von einem Typ in einen anderen oft die Anwendung spezieller Regeln, um solche Besonderheiten zu berücksichtigen. Im Allgemeinen hängt das genaue Ergebnis einer Typumwandlung vor allem von den Eigenschaften des Zieltyps ab.
Konvertierungen in _Bool
Jeder Wert eines beliebigen Skalartyps kann in _Bool
umgewandelt werden. Das Ergebnis ist 0, d.h. false
, wennder Skalarwert gleich 0 ist, und 1, oder true
, wenn er ungleich Null ist. Da ein Null-Zeiger mit Null verglichen wird, wird sein Wert bei der Umwandlung in _Bool
zu false
.
Konvertierungen in vorzeichenlose Ganzzahltypen außer _Bool
Die Werte von Integer bleiben immer erhalten, wenn sie im Bereich des neuen vorzeichenlosen Typs liegen - mit anderen Worten, wenn sie zwischen 0 und U
type
_MAX
liegen, wobei U
type
_MAX
der größte Wert ist, der durch unsigned
dargestellt werden kann. type
.
Bei Werten, die außerhalb des Bereichs des neuen vorzeichenlosen Typs liegen, ist der Wert nach der Konvertierung der Wert, den man durch Addieren oder Subtrahieren (U
type
_MAX
+ 1) so oft wie nötig addiert oder subtrahiert wird, bis das Ergebnis im Bereich des neuen Typs liegt. Das folgende Beispiel veranschaulicht die Zuweisung eines negativen Wertes zu einem vorzeichenlosen Ganzzahltyp:
#include <limits.h>
// Defines the macros USHRT_MAX,
// UINT_MAX, etc.
unsigned
short
n
=
1000
;
// The value 1000 is within the
// range of unsigned short;
n
=
-
1
;
// the value -1 must be converted.
Um einen vorzeichenbehafteten Wert von -1 an den vorzeichenlosen Typ der Variablen anzupassen, addiert das Programm implizit USHRT_MAX
+ 1 dazu, bis ein Ergebnis innerhalb des Typbereichs erzielt wird. Da -1 + (USHRT_MAX
+ 1) = USHRT_MAX
ist, entspricht die letzte Anweisung im vorherigen Beispiel n = USHRT_MAX;
.
Bei positiven ganzzahligen Werten ist die Subtraktion (U
type
_MAX
+ 1) so oft wie nötig, um den Wert in den Bereich des neuen Typs zu bringen, ist dasselbe wie der Rest einer Division durch (U
type
_MAX
+ 1), wie das folgende Beispiel veranschaulicht:
#include <limits.h>
// Defines the macros USHRT_MAX, UINT_MAX, etc.
unsigned
short
n
=
0
;
n
=
0xFEDCBA
;
// The value is beyond the range of
// unsigned short.
Wenn unsigned short
16 Bits breit ist, dann ist sein Maximalwert, USHRT_MAX
, hexadezimal FFFF. Wenn der Wert FEDCBA in unsigned short
umgewandelt wird, ist das Ergebnis dasselbe wie der Rest einer Division durch hexadezimal 10000 (das ist USHRT_MAX
+ 1), der immer FFFF oder weniger ist. In diesem Fall ist der Wert, der n
zugewiesen wird, hexadezimal DCBA.
Um eine reelle Fließkommazahl in einen vorzeichenlosen oder vorzeichenbehafteten Integer-Typ umzuwandeln, verwirft der Compiler den Nachkommaanteil. Wenn der verbleibende ganzzahlige Teil außerhalb des Bereichs des neuen Typs liegt, ist das Ergebnis der Umwandlung undefiniert. Zum Beispiel:
double
x
=
2.9
;
unsigned
long
n
=
x
;
// The fractional part of x is
// simply lost.
unsigned
long
m
=
round
(
x
);
// If x is non-negative, this has the
// same effect as m = x + 0.5;
Bei der Initialisierung von n
in diesem Beispiel wird der Wert von x
von double
in unsigned long
umgewandelt, indem der gebrochene Teil, 0,9, verworfen wird. Der ganzzahlige Teil, 2, ist der Wert, der n
zugewiesen wird. Bei der Initialisierung von m
rundet die C99-Funktion round()
den Wert von x
auf den nächsthöheren oder -niedrigeren ganzzahligen Wert und gibt einen Wert vom Typ double
zurück. Der gebrochene Teil des resultierenden double
Wertes - in diesem Fall 3,0 - ist somit gleich Null, bevor er durch die Typkonvertierung für die Zuweisung an m
verworfen wird.
Wenn eine komplexe Zahl in eine Ganzzahl ohne Vorzeichen umgewandelt wird, wird zunächst der Imaginärteil verworfen. Dann wird die resultierende Fließkommazahl wie zuvor beschrieben umgewandelt. Ein Beispiel:
#include <limits.h>
// Defines macros such as UINT_MAX.
#include <complex.h>
// Defines macros such as the imaginary
// constant I.
unsigned
int
n
=
0
;
float
_Complex
z
=
-
1.7
+
2.0
*
I
;
n
=
z
;
// In this case, the effect is
// the same as n = -1;
// The resulting value of n is UINT_MAX.
Der Imaginärteil von z
wird verworfen, sodass der reale Gleitkommawert -1,7 übrig bleibt. Dann wird auch der gebrochene Teil der Fließkommazahl verworfen. Der verbleibende ganzzahlige Wert, -1, wird in unsigned int
umgewandelt, indem UINT_MAX + 1
addiert wird, so dass der Wert, der schließlich n
zugewiesen wird, gleich bis UINT_MAX
ist.
Konvertierungen in vorzeichenbehaftete Ganzzahltypen
Das Problem der Überschreitung des Wertebereichs des Zieltyps kann auch auftreten, wenn ein Wert von einem Integer-Typ, ob vorzeichenbehaftet oder vorzeichenlos, in einen anderen, vorzeichenbehafteten Integer-Typ konvertiert wird; zum Beispiel, wenn ein Wert vom Typ long
oder unsigned int
in den Typ int
konvertiert wird. Das Ergebnis eines solchen Überlaufs bei der Umwandlung in einen vorzeichenbehafteten Integer-Typ wird, anders als bei der Umwandlung in vorzeichenlose Integer-Typen, der Implementierung überlassen.
Die meisten Compiler verwerfen die höchsten Bits der binären Darstellung des ursprünglichen Werts und interpretieren die niedrigsten Bits entsprechend dem neuen Typ. Wie das folgende Beispiel zeigt, wird bei dieser Umwandlungsstrategie das vorhandene Bitmuster einer unsigned int
als vorzeichenbehafteter int
Wert interpretiert:
#include <limits.h>
// Defines macros such as UINT_MAX
int
i
=
UINT_MAX
;
// Result: i = -1 (in two's complement
// representation)
Je nach Compiler kann ein solcher Umwandlungsversuch jedoch auch dazu führen, dass ein Signal ausgelöst wird, um das Programm über den Überlauf des Wertebereichs zu informieren.
Wenn eine reelle oder komplexe Fließkommazahl in einen vorzeichenbehafteten Ganzzahlentyp umgewandelt wird, gelten dieselben Regeln wie für die Umwandlung in einen vorzeichenlosen Ganzzahlentyp, wie im vorherigen Abschnitt beschrieben.
Konvertierungen in reelle Fließkommatypen
Nicht alle ganzzahligen Werte können in Fließkommatypen genau dargestellt werden. Obwohl zum Beispiel der Wertebereich des Typs float
den Bereich der Typen long
und long long
umfasst, ist float
nur auf sechs Dezimalstellen genau. Daher können einige long
Werte nicht genau in einem float
Objekt gespeichert werden. Das Ergebnis einer solchen Umwandlung ist der nächst niedrigere oder nächst höhere darstellbare Wert, wie das folgende Beispiel zeigt:
long
l_var
=
123456789L
;
float
f_var
=
l_var
;
// Implicitly converts long value
// to float.
printf
(
"The rounding error (f_var - l_var) is %f
\n
"
,
(
double
)
f_var
-
l_var
);
Beachte, dass die Subtraktion in diesem Beispiel mit einer Genauigkeit von mindestens double
durchgeführt wird. Die typische Ausgabe dieses Codes ist:
The
rounding
error
(
f_var
-
l_var
;)
is
3.000000
Jeder Wert in einem Fließkommatyp kann genau in einem anderen Fließkommatyp mit höherer Genauigkeit dargestellt werden. Wenn also ein Wert von double
in long double
umgewandelt wird, oder wenn ein Wert von float
in double
oder long double
umgewandelt wird, bleibt der Wert genau erhalten. Bei der Umwandlung von einem präziseren in einen weniger präzisen Typ kann der umgewandelte Wert jedoch außerhalb des Bereichs des neuen Typs liegen. Wenn der Wert den Bereich des Zieltyps überschreitet, ist das Ergebnis der Umwandlung undefiniert. Wenn der Wert innerhalb des Bereichs des Zieltyps liegt, aber nicht genau in der Genauigkeit des Zieltyps dargestellt werden kann, ist das Ergebnis der nächst kleinere oder nächst größere darstellbare Wert. Das Programm in Beispiel 2-2 veranschaulicht den Rundungsfehler, der durch eine solche Umwandlung in einen weniger genauen Fließkommatyp entsteht.
Wenn eine komplexe Zahl in einen reellen Fließkommatyp umgewandelt wird, wird der Imaginärteil einfach verworfen, und das Ergebnis ist der Realteil der komplexen Zahl, der eventuell weiter in den Zieltyp umgewandelt werden muss, wie in diesem Abschnitt beschrieben.
Konvertierung in komplexe Fließkomma-Typen
Wenn eine Ganzzahl oder eine reelle Fließkommazahl in einen komplexen Typ umgewandelt wird, erhält man den Realteil des Ergebnisses, indem man den Wert in den entsprechenden reellen Fließkommatyp umwandelt, wie im vorherigen Abschnitt beschrieben. Der Imaginärteil ist Null.
Wenn eine komplexe Zahl in einen anderen komplexen Typ umgewandelt wird, werden der Real- und der Imaginärteil getrennt nach den Regeln für reelle Fließkommatypen umgewandelt:
#include <complex.h>
// Defines macros such as the imaginary
// constant I
double
_Complex
dz
=
2
;
float
_Complex
fz
=
dz
+
I
;
In der ersten dieser beiden Initialisierungen wird die ganzzahlige Konstante 2 implizit in double _Complex
umgewandelt, um sie dz
zuzuweisen. Der resultierende Wert von dz
ist 2.0 + 0.0 × I
.
Bei der Initialisierung von fz
werden die beiden Teile des double _Complex
Wertes von dz
(nach der Addition) in float
umgewandelt, so dass der Realteil von fz
gleich 2.0F
ist und der Imaginärteil Teil 1.0F
.
Umwandlung von nichtarithmetischen Typen
Zeiger und die Namen von Arrays und Funktionen unterliegen auch bestimmten impliziten und expliziten Typkonvertierungen. Strukturen und Unions können nicht konvertiert werden, obwohl Zeiger auf sie in und aus anderen Zeigertypen konvertiert werden können.
Array- und Funktionsbezeichner
Ein Array- oder Funktionsbezeichner ist ein Ausdruck, der einen Array- oder Funktionstyp hat. In den meisten Fällen wandelt der Compiler einen Ausdruck mit einem Array-Typ, z. B. den Namen eines Arrays, implizit in einen Zeiger auf das erste Element des Arrays um. Nur in den folgenden Fällen wird der Array-Ausdruck nicht in einen Zeiger umgewandelt:
-
Wenn das Array der Operand des
sizeof
Operators ist -
Wenn das Array der Operand des Adressoperators ist
&
-
Wenn ein String-Literal verwendet wird, um ein Array von
char
,wchar_t
,char16_t
, oderchar32_t
Die folgenden Beispiele zeigen die implizite Umwandlung von Array-Bezeichnern in Zeiger, wobei die Umwandlungsspezifikation %p
verwendet wird, um Zeigerwerte zu drucken:
#include <stdio.h>
int
*
iPtr
=
0
;
// A pointer to int, initialized with 0.
int
iArray
[]
=
{
0
,
10
,
20
};
// An array of int, initialized.
int
array_length
=
sizeof
(
iArray
)
/
sizeof
(
int
);
// The number of
// elements:
// in this case, 3.
printf
(
"The array starts at the address %p.
\n
"
,
iArray
);
*
iArray
=
5
;
// Equivalent to iArray[0] = 5;
iPtr
=
iArray
+
array_length
-
1
;
// Point to the last element of
// iArray: equivalent to
// iPtr = &iArray[array_length-1];
printf
(
"The last element of the array is %d.
\n
"
,
*
iPtr
);
Bei der Initialisierung von array_length
in diesem Beispiel gibt der Ausdruck sizeof(iArray)
die Größe des gesamten Arrays und nicht die Größe eines Zeigers. Der gleiche Bezeichner iArray
wird jedoch in den anderen drei Anweisungen, in denen er vorkommt, implizit in einen Zeiger umgewandelt:
-
Als Argument im ersten Aufruf von
printf()
-
Als Operand des Dereferenzierungsoperators
*
-
Bei den arithmetischen Operationen mit Zeigern und der Zuweisung an
iPtr
(siehe auch "Zeiger ändern und vergleichen")
Die Namen von Zeichen-Arrays werden als Zeiger in String-Operationen verwendet, wie in diesem Beispiel:
#include <stdio.h>
#include <string.h>
// Declares size_t strlen( const char *s )
char
msg
[
80
]
=
"I'm a string literal."
;
// Initialize an array of char.
printf
(
"The string is %d characters long.
\n
"
,
strlen
(
msg
));
// Answer: 21.
printf
(
"The array named msg is %d bytes long.
\n
"
,
sizeof
(
msg
));
// Answer: 80.
Bei dem Funktionsaufruf strlen(msg)
in diesem Beispiel wird der Array-Bezeichner msg
implizit in einen Zeiger auf das erste Element des Arrays mit dem Typ des Funktionsparameters const char *
umgewandelt. Intern zählt strlen()
lediglich die Zeichen, die an dieser Adresse beginnen, bis zum ersten Nullzeichen, dem String-Terminator.
Ebenso kann jeder Ausdruck, der eine Funktion bezeichnet, wie z. B. ein Funktionsname, implizit in einen Zeiger auf die Funktion umgewandelt werden. Auch hier gilt, dass diese Umwandlung nicht gilt, wenn der Ausdruck der Operand des Adressoperators &
ist. Der sizeof
Operator kann nicht mit einem Operanden vom Typ Funktion verwendet werden. Das folgende Beispiel veranschaulicht die implizite Umwandlung von Funktionsnamen in Zeiger (das Programm initialisiert ein Array mit Zeigern auf Funktionen und ruft die Funktionen dann in einer Schleife auf):
#include <stdio.h>
void
func0
()
{
puts
(
"This is the function func0(). "
);
}
void
func1
()
{
puts
(
"This is the function func1(). "
);
}
/* ... */
void
(
*
funcTable
[
2
])(
void
)
=
{
func0
,
func1
};
// Array of two pointers
// to functions
// returning void.
for
(
int
i
=
0
;
i
<
2
;
++
i
)
// Use the loop counter as the array
funcTable
[
i
]();
// index.
Explizite Zeigerumwandlungen
Um einen Zeiger von einem Zeigertyp in einen anderen zu konvertieren, musst du normalerweise einen expliziten Cast verwenden. In einigen Fällen bietet der Compiler eine implizite Umwandlung an, wie in "Implizite Zeigerumwandlungen" beschrieben . Zeiger können auch explizit in Ganzzahlen umgewandelt werden und umgekehrt.
Objekt-Zeiger
Du kannst einen Objektzeiger - d.h. einen Zeiger auf einen vollständigen oder unvollständigen Objekttyp - explizit in einen anderen Objektzeigertyp umwandeln. In deinem Programm musst du sicherstellen, dass die Verwendung des konvertierten Zeigers sinnvoll ist. Hier ist ein Beispiel:
float
f_var
=
1.5F
;
long
*
l_ptr
=
(
long
*
)
&
f_var
;
// Initialize a pointer to long with
// the address of f_var.
double
*
d_ptr
=
(
double
*
)
l_ptr
;
// Initialize a pointer to double
// with the same address.
// On a system where sizeof(float) equals sizeof(long):
printf
(
"The %zu bytes that represent %f, in hexadecimal: 0x%lX
\n
"
,
sizeof
(
f_var
),
f_var
,
*
l_ptr
);
// Using a converted pointer in an assignment can cause trouble:
/* *d_ptr = 2.5; */
// Don't try this! f_var's location doesn't
// have space for a double value!
*
(
float
*
)
d_ptr
=
2.5
;
// OK: stores a float value in that location.
Wenn der Objektzeiger nach der Umwandlung nicht die vom neuen Typ geforderte Ausrichtung hat, sind die Ergebnisse der Verwendung des Zeigers undefiniert. In allen anderen Fällen wird durch die Rückkonvertierung des Zeigerwerts in den ursprünglichen Zeigertyp garantiert, dass er dem ursprünglichen Zeiger entspricht.
Wenn du einen beliebigen Objektzeiger in einen Zeiger auf einen beliebigen char
Typ (char
, signed char
, oder unsigned char
) umwandelst, ist das Ergebnis ein Zeiger auf das erste Byte des Objekts. Das erste Byte wird hier als das Byte mit der niedrigsten Adresse betrachtet, unabhängig von der Bytereihenfolge des Systems. Im folgenden Beispiel wird diese Funktion verwendet, um einen hexadezimalen Dump einer Strukturvariablen zu drucken:
#include <stdio.h>
struct
Data
{
short
id
;
double
val
;
};
struct
Data
myData
=
{
0x123
,
77.7
};
// Initialize a
// structure.
unsigned
char
*
cp
=
(
unsigned
char
*
)
&
myData
;
// Pointer to the
// first byte of
// the structure.
printf
(
"%p: "
,
cp
);
// Print the starting
// address.
for
(
int
i
=
0
;
i
<
sizeof
(
myData
);
++
i
)
// Print each byte
printf
(
"%02X "
,
*
(
cp
+
i
)
);
// of the structure,
putchar
(
'\n'
);
// in hexadecimal.
Dieses Beispiel ergibt eine Ausgabe wie die folgende:
0xbffffd70: 23 01 00 00 00 00 00 00 CD CC CC CC CC 6C 53 40
Die Ausgabe der ersten beiden Bytes, 23 01
, zeigt, dass der Code auf einem Little-Endian-System ausgeführt wurde: Das Byte mit der niedrigsten Adresse in der Struktur myData
war das niederwertigste Byte des Mitglieds short
id
.
Funktionszeiger
Der Typ einer Funktion enthält immer ihren Rückgabetyp und kann auch ihre Parametertypen enthalten. Du kannst einen Zeiger auf eine bestimmte Funktion explizit in einen Zeiger auf eine Funktion eines anderen Typs umwandeln. Im folgenden Beispiel definiert die Anweisung typedef
einen Namen für den Typ "Funktion, die einen double
Parameter hat und einen double
Wert zurückgibt":
#include <math.h>
// Declares sqrt() and pow().
typedef
double
(
func_t
)(
double
);
// Define a type named func_t.
func_t
*
pFunc
=
sqrt
;
// A pointer to func_t, initialized
// with the address of sqrt().
double
y
=
pFunc
(
2.0
);
// A correct function call by pointer.
printf
(
"The square root of 2 is %f.
\n
"
,
y
);
pFunc
=
(
func_t
*
)
pow
;
// Change the pointer's value to
// the address of pow().
/* y = pFunc( 2.0 ); */
// Don't try this: pow() takes two
// arguments.
In diesem Beispiel werden dem Funktionszeiger pFunc
die Adressen von Funktionen zugewiesen, die unterschiedliche Typen haben. Wenn das Programm jedoch den Zeiger verwendet, um eine Funktion mit einer Definition aufzurufen, die nicht genau dem Typ des Funktionszeigers entspricht, ist das Verhalten des Programms undefiniert.
Implizite Zeigerumwandlungen
Der Compiler
wandelt bestimmte Arten von Zeigern implizit um. Zuweisungen, bedingte Ausdrücke, die die Gleichheitsoperatoren ==
und !=
verwenden, und Funktionsaufrufe beinhalten in drei Fällen eine implizite Zeigerumwandlung, die in den folgenden Abschnitten einzeln beschrieben werden. Die drei Arten der impliziten Zeigerumwandlung sind:
-
Jeder Objektzeigertyp kann implizit in einen Zeiger auf
void
umgewandelt werden und umgekehrt. -
Jeder Zeiger auf einen bestimmten Typ kann implizit in einen Zeiger auf eine qualifiziertere Version dieses Typs umgewandelt werden, d.h. einen Typ mit einem oder mehreren zusätzlichen Typqualifikatoren.
-
Eine Null-Zeiger-Konstante kann implizit in einen beliebigen Zeigertyp umgewandelt werden.
Zeiger auf void
Die Zeiger bis void
- also Zeiger des Typs void *
- werden als "Mehrzweck"-Zeiger verwendet, um die Adresse eines beliebigen Objekts zu repräsentieren, ohne Rücksicht auf dessen Typ. Die Funktion malloc()
gibt zum Beispiel einen Zeiger auf void
zurück (siehe Beispiel 2-3). Bevor du auf den Speicherblock zugreifen kannst, muss der Zeiger void
immer in einen Zeiger auf ein Objekt umgewandelt werden.
Beispiel 4-1 demonstriert weitere Verwendungen von Zeigern auf void
. Das Programm sortiert ein Array mit der Standardfunktion qsort()
, die in der Header-Datei stdlib.h mit dem folgenden Prototyp deklariert ist:
void
qsort
(
void
*
array
,
size_t
n
,
size_t
element_size
,
int
(
*
compare
)
(
const
void
*
,
const
void
*
)
)
;
Die Funktion qsort()
sortiert das Array in aufsteigender Reihenfolge, beginnend mit der Adresse array
mit Hilfe des Schnellsortieralgorithmus. Es wird angenommen, dass das Array aus n
Elemente, deren Größe element_size
.
Der vierte Parameter, compare
ist ein Zeiger auf eine Funktion, die qsort()
aufruft, um zwei beliebige Array-Elemente zu vergleichen. Die Adressen der beiden zu vergleichenden Elemente werden dieser Funktion in ihren Zeigerparametern übergeben. Normalerweise muss diese Vergleichsfunktion vom Programmierer definiert werden. Sie muss einen Wert zurückgeben, der kleiner, gleich oder größer als 0 ist, um anzuzeigen, ob das erste Element kleiner, gleich oder größer als das zweite ist.
Beispiel 4-1. Eine Vergleichsfunktion für qsort()
#include <stdlib.h>
#define ARR_LEN 20
/*
* A function to compare any two float elements,
* for use as a call-back function by qsort().
* Arguments are passed by pointer.
*
* Returns: -1 if the first is less than the second;
* 0 if the elements are equal;
* 1 if the first is greater than the second.
*/
int
floatcmp
(
const
void
*
p1
,
const
void
*
p2
)
{
float
x
=
*
(
float
*
)
p1
,
y
=
*
(
float
*
)
p2
;
return
(
x
<
y
)
?
-
1
:
((
x
==
y
)
?
0
:
1
);
}
/*
* The main() function sorts an array of float.
*/
int
main
()
{
/* Allocate space for the array dynamically: */
float
*
pNumbers
=
malloc
(
ARR_LEN
*
sizeof
(
float
)
);
/* ... Handle errors, initialize array elements ... */
/* Sort the array: */
qsort
(
pNumbers
,
ARR_LEN
,
sizeof
(
float
),
floatcmp
);
/* ... Work with the sorted array ... */
return
0
;
}
In Beispiel 4-1 gibt die Funktion malloc()
ein void *
zurück, das bei der Zuweisung an pNumbers
implizit in float *
umgewandelt wird. Beim Aufruf von qsort()
wird das erste Argument pNumbers
implizit von float *
in void *
umgewandelt, und der Funktionsname floatcmp
wird implizit als Funktionszeiger interpretiert. Wenn die Funktion floatcmp()
schließlich von qsort()
aufgerufen wird, erhält sie Argumente vom Typ void *
, dem "universellen" Zeigertyp, und muss sie explizit in float *
umwandeln, bevor sie sie derefenziert, um ihre float
Variablen zu initialisieren.
Zeiger auf qualifizierte Objekttypen
Die Typqualifizierer in C sind const
, volatile
und restrict
(siehe Kapitel 11 für Details zu diesen Qualifizierern). Der Compiler wandelt zum Beispiel jeden Zeiger auf int
implizit in einen Zeiger auf const int
um, wenn dies notwendig ist. Wenn du jedoch eine Qualifikation entfernen willst, anstatt eine hinzuzufügen, musst du eine explizite Typumwandlung verwenden, wie das folgende Beispiel zeigt:
int
n
=
77
;
const
int
*
ciPtr
=
0
;
// A pointer to const int.
// The pointer itself is not constant!
ciPtr
=
&
n
;
// Implicitly converts the address to the type
// const int *.
n
=
*
ciPtr
+
3
;
// OK: this has the same effect as n = n + 3;
*
ciPtr
*=
2
;
// Error: you can't change an object referenced by
// a pointer to const int.
*
(
int
*
)
ciPtr
*=
2
;
// OK: Explicitly converts the pointer into a
// pointer to a nonconstant int.
Die vorletzte Anweisung in diesem Beispiel verdeutlicht, warum Zeiger auf const
-qualifizierte Typen manchmal als Nur-Lese-Zeiger bezeichnet werden: Du kannst zwar die Werte der Zeiger ändern, aber nicht die Objekte, auf die sie zeigen.
Null-Zeiger-Konstanten
Eine Null-Zeiger-Konstante ist eine Integer-Konstante mit dem Wert 0 oder ein konstanter Integer-Wert von 0, der als Zeiger auf void
gecastet wird. Das Makro NULL
ist in den Header-Dateien stdlib.h, stdio.h und anderen als Null-Zeiger-Konstante definiert. Das folgende Beispiel veranschaulicht die Verwendung des Makros NULL
als Zeigerkonstante zur Initialisierung von Zeigern anstelle einer ganzzahligen Null oder eines Nullzeichens:
#include <stdlib.h>
long
*
lPtr
=
NULL
;
// Initialize to NULL: pointer is not ready
// for use.
/* ... operations here may assign lPtr an object address ... */
if
(
lPtr
!=
NULL
)
{
/* ... use lPtr only if it has been changed from NULL ... */
}
Wenn du eine Null-Zeiger-Konstante in einen anderen Zeigertyp umwandelst, wird das Ergebnis als Null-Zeiger bezeichnet. Das Bitmuster eines Null-Zeigers ist nicht unbedingt Null. Wenn du jedoch einen Null-Zeiger mit Null, mit NULL
oder mit einem anderen Null-Zeiger vergleichst, ist das Ergebnis immer true
. Umgekehrt ergibt der Vergleich eines Null-Zeigers mit einem gültigen Zeiger auf ein Objekt oder eine Funktion immer false
.
Konvertierungen zwischen Zeiger- und Ganzzahltypen
Du kannst einen Zeiger explizit in einen Integer-Typ umwandeln und andersherum. Das Ergebnis solcher Konvertierungen hängt vom Compiler ab und sollte mit der Adressierungsstruktur des Systems übereinstimmen, auf dem die kompilierte ausführbare Datei läuft. Konvertierungen zwischen Zeiger- und Ganzzahltypen können bei der Systemprogrammierung nützlich und notwendig sein, wenn Programme auf bestimmte physikalische Adressen zugreifen müssen, z. B. auf ROM oder speicherbelegte E/A-Register.
Wenn du einen Zeiger in einen Ganzzahltyp umwandelst, dessen Bereich nicht groß genug ist, um den Wert des Zeigers darzustellen, ist das Ergebnis undefiniert. Umgekehrt führt die Umwandlung einer Ganzzahl in einen Zeigertyp nicht unbedingt zu einem gültigen Zeiger. In der Header-Datei stdint.h können optional die Integer-Typen intptr_t
(mit Vorzeichen) und uintptr_t
(ohne Vorzeichen) definiert werden. Jeder gültige Zeiger kann in einen dieser Typen umgewandelt werden, und eine anschließende Umwandlung zurück in einen Zeiger ergibt garantiert den ursprünglichen Zeiger. Du solltest daher immer einen dieser Typen verwenden, wenn stdint.h sie definiert, wenn du Konvertierungen zwischen Zeigern und Ganzzahlen durchführen musst.
Hier sind ein paar Beispiele:
float
x
=
1.5F
,
*
fPtr
=
&
x
;
// A float, and a pointer to it.
// Save the pointer's value as an integer:
unsigned
long
long
adr_val
=
(
unsigned
long
long
)
fPtr
;
// Or, if stdint.h has been included and uintptr_t is defined:
uintptr_t
adr_val
=
(
uintptr_t
)
fPtr
;
/*
* On an Intel x86 PC in DOS, the BIOS data block begins at the
* address 0x0040:0000. The first two-byte word at that address
* contains the I/O address of the serial port COM1.
* (Compile using DOS's "large" memory model.)
*/
unsigned
short
*
biosPtr
=
(
unsigned
short
*
)
0x400000L
;
unsigned
short
com1_io
=
*
biosPtr
;
// The first word contains the
// I/O address of COM1.
printf
(
"COM1 has the I/O base address %Xh.
\n
"
,
com1_io
);
Die letzten drei Anweisungen beziehen Informationen über die Hardwarekonfiguration aus der Systemdatentabelle, vorausgesetzt, die Betriebsumgebung erlaubt dem Programm den Zugriff auf diesen Speicherbereich. In einem DOS-Programm, das mit dem großen Speichermodell kompiliert wurde, sind Zeiger 32 Bit breit und bestehen aus einer Segmentadresse in den oberen 16 Bit und einem Offset in den unteren 16 Bit (oft in der Form segment
:offset
). Daher kann der Zeiger biosPtr
im vorherigen Beispiel mit einer Long-Integer-Konstante initialisiert werden.
Get C in a Nutshell, 2. Auflage now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.