Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (2024)

Zurück zu Grundlagen | Hoch zu Inhaltsverzeichnis | Vor zu Datatype-/Storage Class Specifier

Die Programmiersprache C kennt nachfolgende grundlegende Datentypen und Zeiger auf diese Datentypen (siehe auch Grundlagen:Grundlegende Datentypen und Typsicherheit)

DatentypBsp. für DatentypBsp. für Zeiger auf Datentyp
Ganzzahl
 char var; int var; short int var; long int var;long long int var;
 char *ptr; int *ptr; short int *ptr; long int *ptr;long long int *ptr;
Gleitkommazahl
 double var; float var;long double var;
 double *ptr; float *ptr;long double *ptr;
Funktionen
void func(void) {}
void (*pfunc)(void);
Arrays
int arr[10];float arr[3][3];
int (*parr);float (*parr)[3];void (*pfunc[5])(void);
Strukturen/Unions
struct xyz {int x,y,z};union abc {int a,b,c};
struct xyz *ptr;union abc *ptr;
Aufzählungstyp
enum abc {A,B,C};
enum abc *ptr;
Boolescher Datentyp
_Bool var;
_Bool *ptr;
Komplexe Zahlen
_Complex var;
_Complex *ptr;

Bis auf die Datentypen Funktionen, Array und die Zeiger sollen die wesentlichen Aspekte dieser Datentypen im nachfolgenden beschrieben werden.

Der grundlegende Datentyp ist 'int'!

  • Es wird mit nichts Kleinerem gerechnet als mit dem Datentyp 'int' (siehe Implizite Typumwandlung):
char var1=10,var2=20,var3;var3=var1+var2; //Addition findet auf Basis des Datentyps int statt
  • Es gibt zwar den Booleschen Datentyp, dieser entspricht jedoch einem Ganzzahldatentyp und true und false entsprechen den Integerwerten 0 und 1
  • Der Aufzählungsdatentyp und seine Aufzählungselemente sind vom Datentyp 'int'
  • Bis einschl. C90 wurde bei der Definition von Variablen ohne Angabe des Datentyps, bei Definition einer Funktion ohne Angabe des Datentyps der Übergabeparameter, des Rückgabedatentyps der Datentyp implizit auf int gesetzt (implizit int)
static var2=2;foo(par1,par2){ auto var1; var1=par1+par2; return var1;}

Öffnen im Compiler Explorer

Ganzzahl Datentypen

[Bearbeiten]

Grundlegend kennt die Programmiersprache C nur zwei Ganzzahldatentypen:

  • Datentyp: int
Vorzeichenbehafteter (signed) Datentyp, dessen Bitbreite von der Rechnerarchitektur und des Compilers abhängig ist
  • Datentyp char
Datentyp mit einer Mindestbreite von 8-Bit u.A. zur Speicherung von Zeichen typischerweise im ASCII Format. Die C-Spezifikation [C11 6.2.5 Types] lässt es dem Compiler frei, ob char als vorzeichenbehafteter oder vorzeichenloser Datentyp implementiert wird.
Ergänzend kann der Datentyp char auch zur Speicherung von ganzen Zahlen genutzt werden. In diesem Fall empfiehlt sich, diese explizit als signed und unsiged zu definieren. Wenn nur ASCII Zeichen gespeichert werden, so ist diese Angabe nicht nötig!

Optional können Qualifier dem Datentyp vorangestellt werden:

  • unsigned / signed zur Angabe, ob der Inhalt als vorzeichenlose Zahl oder als vorzeichenbehaftete Zahl zu interpretieren ist. Ohne Angabe sind Variablen vom Datentyp int vorzeichenbehaftet
  • short / long / long long (letzteres seit C99) als vorangestellten Qualifier zu int zur Vergrößerung/Verkleinerung der Bitbreite. Bei Angabe des Qualifiers ist das Schlüsselwort int optional. Die Beschreibung long long int und long long sind gleichbedeutend!

Als Bitbreite für die Datentypen ist im C-Standard nur ein Mindestbreite definiert. Die genaue Bitbreite hängt von der Rechenbreite des Systems und vom Compiler ab (siehe [C11 6.2.5 Types]):

DatentypeMindest
breite
Typisch bei 32-Bit
Architektur
Typisch bei 64-Bit
Architektur
printf Format
Anweisung
char888%c
signed char
unsigned char
888%hhd
%hhu
short
short int
161616%hd / %hu
int1616/3232%d / %u
long
long int
323232/64%ld / %lu
long long
long long int
646464%lld / %llu

In der Header-Datei limits.h sind die tatsächlichen Grenzwerte der Datentypen gespeichert:

KonstanteBeschreibungTyp. bei 64-Bit Architektur
CHAR_BITAnzahl der Bits in einem Char8
SCHAR_MINmin. Wert, den der Typ bei signed char aufnehmen kann-128
SCHAR_MAXmax. Wert, den der Typ bei signed char aufnehmen kann127
UCHAR_MAXmax. Wert, den der Typ bei unsigned char aufnehmen kann255
CHAR_MINmin. Wert, den der Typ char aufnehmen kann0 oder SCHAR_MIN
CHAR_MAXmax. Wert, den der Typ char aufnehmen kannSCHAR_MAX oder UCHAR_MAX
SHRT_MINmin. Wert, den der Typ short int annehmen kann-32.768
SHRT_MAXmax. Wert, den der Typ short int annehmen kann32.767
USHRT_MAXmax. Wert, den der Typ unsigned short int annehmen kann65.535
INT_MINmin. Wert, den der Typ int annehmen kann-2.147.483.648
INT_MAXmax. Wert, den der Typ int annehmen kann2.147.483.647
UINT_MAXmax. Wert, den der Typ unsigned int aufnehmen kann4.294.967.296
LONG_MINmin. Wert, den der Typ long int annehmen kann-2.147.483.648 oder
-9.223.372.036.854.775.808
LONG_MAXmax. Wert, den der Typ long int annehmen kann2.147.483.647 oder
9.223.372.036.854.775.807
ULONG_MAXmax. Wert, den der Typ unsigned long int annehmen kann4.294.967.296 oder
18.446.744.073.709.551.616
LLONG_MINmin. Wert, den der Typ long long int annehmen kann-9.223.372.036.854.775.808
LLONG_MAXmax. Wert, den der Typ long long int annehmen kann9.223.372.036.854.775.807
ULLONG_MAXmax. Wert, den der Typ unsigned long long int annehmen kann18.446.744.073.709.551.615

Aufgrund dessen, dass die Breite der Datentypen von der Rechnerarchitektur und vom Compiler abhängig ist, sind in der Header-Datei stdint.h Aliase für Datentypen enthalten, welche in ihrem Alias die tatsächliche Breite und das Vorzeichens beinhalten:

uint8_t --> unsigned 8-Bit int8_t --> signed 8-Bituint16_t --> unsigned 16-Bit int16_t --> signed 16-Bit...

Für C++ sehen diese Datentypen wie folgt aus:

std::intptr_tstd::int8_t std::uint8_tstd::int16_t std::uint16_tstd::int32_t std::uint32_tStd::int64_t std::uint64_t

Zur Erzeugung von portablen/rechnerunabhängigen Programmen empfiehlt sich, diese Datentypen zu nutzen. Die Rechenbreite ist dann unabhängig vom Compiler und Betriebssystem.

Gleitkomma Datentypen

[Bearbeiten]

Gleitkommazahlen werden im Computer auf Basis folgender Schreibweise dargestellt (siehe auch: Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (4)Gleitkommazahl):

 Dezimalsystem Dualsystem12,3456710 = 1234567 * 10^-5 0,110 = 1,1001100110011 * 2^-4 ------- -- --------------- -- Mantisse Exponent Mantisse Exponent
Mantisse
Vorzeichenlose ganze Zahl (ggf. mit einer festen Position des Dezimalpunktes=Festkommazahl)
Exponent
Vorzeichenbehaftete ganze Zahl, welche um einen Bias verschoben ist
Vorzeichen
Vorzeichen der (vorzeichenlosen) Mantisse

Zur Speicherung im Computer werden das Vorzeichen, die Mantisse und der Exponent getrennt im Binärformat gespeichert, für den Programmierer aber als eine (zusammenhängende) Zahl dargestellt:

Gleitkommazahlen haben genauso wie ganze Zahlen einen beschränkten Wertebereich:

  • Die Anzahl der Bits der Mantisse bestimmen die Anzahl der Nachkommastellen:
Bei 23-Bit Mantisse hat das niederwertigste Bit die Wertigkeit:2^-22=1/2^22=1/(2^10*2^10*2^2)=1/(1024*1024*4)1/(4.000.000)=0,000.000.25 Es könnten also nur 7..8 dezimale Nachkommastellen gespeichert werden
  • Mit dem Exponent kann 'quasi' der kleinste und der größte Wertebereich angegeben werden:
Bei 8-Bit Exponent mit einem Bias von 127 liegt der Wertebereich desExponenten im Bereich von -126  +127 (Exponent -127 und +127 wird zur Darstellung weitere Zahlen benötigt).Die kleinste darstellbare Zahl (unter Vernachlässigung der Nachkommastellen der Mantisse ist:2^-126=1/2^1261,175*10^-38Die größte darstellbare Zahl ist:2^127 1,7*10^38

Öffnen im Compiler Explorer

Die genaue Darstellung (Anzahl der Bits für Mantisse und Exponent, Darstellung von NAN/INF, ….) ist in C nicht spezifiziert und somit compiler- und rechnerabhängig. Zumeist wird mittlerweile die Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (6)IEEE 754 Standardisierung genutzt (Norm wurde von Intel mit der Entwicklung der 8087 FPU entworfen).

Grunddatentypen und der Wertebereich (nach Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (7)IEEE 754)

Daten-
type
Speicher-
platz
ExponentMantisseGrößte ZahlKleinste ZahlGenauigkeitprintf Format Anweisung
float4 Byte8 bit23 bit+-3,4*10381,2*10-386 Stellen---
double8 Byte11 bit52 bit+-1,7*103082,3*10-30812 Stellen%f %e %g
long double10 Byte≥8 bit≥ 63 bit+-1,1*1049323,4*10-493218 Stellen%Lf %Le %Lg

Aufgrund des nicht standardisierten Formates sollte ein Gleitkommadatentyp nicht im Binärformat für den Datenaustausch (mit Netzwerk, über Dateien, ..) genutzt werden. Andere Rechnersysteme/Programmiersprachen würde aufgrund der anderen Interpretation andere Zahlenwerte aus den übertragenen/gespeicherten Binärdaten auslesen!

float var1;write(file_hdl,&var1,sizeof var1); //Schreiben des Binärwertes //in eine Dateidouble var2;send(socket_hdl,&var2,sizeof var2,0);//Senden des Binärwertes //über ein Netzwerk

Die Standard-C-Library bietet einige mathematische Funktionen wie sin() / cos() / tan() / atan2() / sqrt() / pow() / exp()/… an. Die Prototypen dieser Funktion sind in der Header-Datei math.h beschrieben, so dass diese bei Nutzung dieser Funktion zu inkludieren ist. Ergänzend ist die Shared Library libm.so über den Compilerschalter '-lm' einzubinden:

#include <math.h>//Math-Library libm.so mittels Linker-Command '-lm' einbindenint main(int argc,char *argv[]) { double var1=47.11; double var2=sqrt(var1);

Öffnen im Compiler Explorer

Die Funktionen basieren auf den Datentyp double, d.h. sowohl der Übergabewert als auch der Rückgabewert ist vom Datentyp double. Die Prototypen sehen wie folgt aus:

double sin(double);double cos(double);double tan(double);double asin(double);double acos(double);double atan(double); //Wertebereich der Rückgabe von -PI/2..+PI/2double atan2(double,double); //Wertebereich der Rückgabe von -Pi .. +PIdouble sqrt(double);double log(double);

Über den Suffix f oder l im Funktionsnamen kann der zugrundeliegende Datentyp auf float (Suffix f) oder long double (Suffix l) geändert werden:

float sinf(float);long double sinl(long double);

Ergänzend zu den Prototypen sind die gebräuchlichen Naturkonstanten in math.h definiert (siehe auch: math.h):

M_E Value of eM_LOG2E Value of log_2 eM_LOG10E Value of log_10 eM_LN2 Value of log_e 2M_LN10 Value of log_e 10M_PI Value of πM_PI_2 Value of π/2M_PI_4 Value of π/4M_1_PI Value of 1/πM_2_PI Value of 2/πM_2_SQRTPI Value of 2/πM_SQRT2 Value of 2M_SQRT1_2 Value of 1/2

Gleitkommazahlen können nicht nur Zahlenwerte, sondern auch Sonderwerte annehmen. Diese Sonderwerte sind ebenfalls in math.h beschrieben (siehe auch: math.h):

INFINITY A constant expression of type float representing positive or unsigned infinity, if available; else a positive constant of type float that overflows at translation time.NAN A constant expression of type float representing a quiet NaN. This macro is only defined if the implementation supports quiet NaNs for the float type.

Mit fesetround()/fegetround() kann gesetzt/gelesen werden, wie mit Ergebnissen umzugehen sind, die nicht exakt darstellbar sind. Mögliche Werte sind 'round to nearest' (default), 'round up', 'round down' und 'round toward zero'.

Komplexe Zahlen

[Bearbeiten]

Nach dem Wikipedia Artikel Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (8)komplexe Zahlen stellen komplexe Zahlen eine Erweiterung der reellen Zahlen dar. Ziel der Erweiterung ist es, algebraische Gleichungen wie x2+1=0 bzw. x2=-1 lösbar zu machen. ... Da die Quadrate aller reellen Zahlen größer oder gleich 0 sind, kann die Lösung der Gleichung x2=-1 keine reelle Zahl sein. Man braucht eine ganz neue Zahl, die man üblicherweise i nennt, mit der Eigenschaft i2=-1. Diese Zahl i wird als imaginäre Einheit bezeichnet.Komplexe Zahlen werden nun als Summe a+b*i definiert, wobei a und b reelle Zahlen sind und i die oben definierte imaginäre Einheit ist.
Der Datentyp für komplexe Zahlen (erst ab C99 enthalten) beinhaltet folglich zwei Gleitkommazahlen, eine für den reelen Teil und eine für den imaginären Teil. Entsprechend den Gleitkommazahlen steht der komplexe Datentyp ebenfalls in drei unterschiedlichen Genauigkeiten zur Verfügung:

DatentypeSpeicherplatz
float _Complex2x4Byte
double _Complex
_Complex
2x8Byte
long double _Complex2x10Byte

Wie auch beim Booleschen Datentyp ist in der Header-Datei complex.h für den einfacheren Umgang mit diesem Datentyp das Makro complex als Textersetzung für _Complex gesetzt.

Zur Darstellung von komplexen Konstanten wurde die Gleitkommakonstante um den Suffix i ergänzt. Durch Anhängen von i wird aus dem ansonsten rellen Teil der imaginäre Teil und damit aus der Gleitkommakonstante eine komplexe Gleitkommakonstante:

_Complex varc1=2i;_Complex varc2=1+2i;printf("%f %f\n",creal(varc1),cimag(varc1));printf("%f %f\n",creal(varc2),cimag(varc2));

Öffnen im Compiler Explorer

Normale Gleitkommavariablen werden als reellen Teil einer komplexen Zahl angesehen. Bei Operationen von Gleitkommazahlen und komplexen Zahlen wird der imaginäre Teil der Gleitkommazahl auf 0 gesetzt. Beim Zuweisen einer komplexen Zahl an eine Gleitkommazahl wird nur der reele Teil 'gelesen'.
Soll eine Gleitkommavariablen zu einem imaginären Teil gewandelt werden, so muss diese bspw mit der Konstanten 1.0i multipliziert werden:

double vard1=2i; //Vorsicht, es wird nur der reele Teilprintf("%f\n",vard1); //der komplexen Zahl in vard1 gespeichert!_Complex varc1=1+2i; vard1=1;varc1=varc1+vard1;printf("%f %f\n",creal(varc1),cimag(varc1));varc1=varc1+vard1*1.0i;printf("%f %f\n",creal(varc1),cimag(varc1));

Öffnen im Compiler Explorer

Die Standard-C-Library bietet diverse mathematische Funktionen wie csin(), ccos(), csqrt(), cpow() und ergänzend Umrechnungsfunktionen von Gleitkommazahlen in komplexe Zahlen (und umgedreht) wie creal(),cimag() ,cabs() für den Umgang mit komplexen Zahlen an:

double _Complex csin(double _Complex);double _Complex csqrt(double _Complex);double creal(double _Complex);double cimag(double _Complex);

Zur Nutzung dieser Funktionen muss die Header-Datei complex.h inkludiert und ergänzend die Shared Library libm.so über den Compilerschalter '-lm' eingebunden werden.

#include <complex.h>//Math-Library mittels Linker-Command '-lm' einbindenint main(int argc, char *argv[]) { double _Complex xyz=6+2i; //Reeler-Teil=6 Imaginärer Teil=2 printf("Reeller Teil: %f Imaginärer Teil:%f",creal(xyz),cimag(xyz));

Öffnen im Compiler Explorer

Die Funktionen beruhen auf den Datentyp double _Complex, d.h. sowohl der Übergabewert als auch der Rückgabewert ist double _Complex resp. double (bei den Umrechnungsfunktionen). Über den Suffix f und l im Funktionsnamen kann der zugrundeliegende Datentyp geändert werden:

float crealf(float _Complex z);long double creall(long double _Complex z);

Hinweis:

  • Der Datentyp _Complex und die dazugehörigen mathematischen Funktionen sind in der C-Spezifikation als optional gekennzeichnet.

Boolschescher Datentyp

[Bearbeiten]

Siehe Grundlagen: Boolescher-Datentype/Operatoren

void / unvollständiger Datentype

[Bearbeiten]

Der Datentyp void dient vorrangig dazu, nicht vorhandene Über- und Rückgabewerte von Funktionen anzuzeigen. Eine Variable vom Datentyp void kann nicht angelegt werden. Ein Zeiger vom Datentyp void ist möglich, kann aber nicht dereferenziert werden (siehe Kap. Zeiger:Void-Zeiger)

void func(void); //Void zur Darstellung der nicht vorhandenen Parameter und //des nicht vorhandenen Rückgabewertesvoid var; //Eine Variable vom Datentyp void kann nicht angelegt werdenvoid *ptr; //Void-Zeiger

Öffnen im Compiler Explorer

Hinweise:

  • sizeof(void) ist nach der C-Spezifikation nicht erlaubt. Dennoch geben viele Compiler hier den Wert 1 zurück!
  • Mit einem expliziten Cast auf den Datentyp void wird dem Compiler mitgeteilt, dass diese Variable in Gebrauch ist, der Wert in dieser Operation aber nicht genutzt wird. Hierüber kann die Compilerwarning 'unused Variable' unterbunden werden:
void func(int par1, int par2) { int var1=12; int var2=13; (void)par2; //Angabe, dass diese Variable genutzt wird. (void)var2; //Angabe, dass diese Variable genutzt wird. if(var1==par1) //var1 und par1 werden verwendet

Öffnen im Compiler Explorer

Datentypkonvertierung

[Bearbeiten]

Die Abarbeitungsreihenfolge der Operatoren wird durch die Prioritätenliste/Rangfolge (siehe C-Programmierung:Liste der Operatoren nach Priorität) vorgegeben. Operatoren mit höherer Priorität werden vor Operatoren mit niedriger Priorität ausgeführt:

//Der Ausdruckc = sizeof(x) + ++a / 3; //wird aufgrund der Prioritäten wie folgt ausgewertet:c= (sizeof(x)) + ( (++a) / 3);

Öffnen im Compiler Explorer

Bei identischer Priorität ergibt sich die Abarbeitungsreihenfolge aus der Assoziativität (L-R oder R-L)

a=33 / 5 / 2;//Wird aufgrund der Assoziativität wie folgt ausgewertet.//a= (33 / 5) / 2;//und damit zu 3 und nicht zu 16 (bei 33 / (5/2)) ausgewertet. a = b = c = d*2; //→ a=(b=(c=(d*2)));a = b = 1+c = d; //→ a=(b=((1+c)=d)); //Compilerfehler, da  //1+c kein lvalue ist

Öffnen im Compiler Explorer

Für das Rechnen/Vergleichen müssen beide Operatoren vom identischen Datentyp sein! Sind diese nicht identisch, so müssen die Datentypen 'angeglichen' werden. Dies kann einerseits ‚automatisch‘ mittels 'implizierter' Typumwandlung oder ‚manuell‘ mittels ‚expliziter' Typumwandlung erfolgen.
Beim Zuweisungsoperator (inkl. Parameterzuweisung bei Funktionsaufrufen und Funktionsrückgabewerte) gilt dies ebenso, nur dass hier der Quelldatentyp an den Zieldatentyp angepasst werden muss. Die impliziten Regeln finden hier keine Anwendung.

Hinweis:

  • Empfehlenswert ist, die Datentypen der Variablen so zu wählen, dass der Compiler keine implizite Typumwandlung tätigt. Kann dies nicht vermieden werden, so sollte die explizite Typumwandlung genutzt werden (um sich einen möglichen Datenverlust bewusst zu machen).
  • Der Datentyp selbst sollte den möglichen Wertebereich der Variablen entsprechen und nicht unnötig groß gewählt werden.
Datentyp zur Speicherung der Stundenzeit: Wertebereich: 0 ... 23 -> unsigned charDatentyp zur Speicherung einer Jahreszahl: Wertebereich: 2000 v.C. ... 4000 n.C -> signed shortDatentyp zur Speicherung einer Temperatur: Wertebreich: -273,0°C ... 2000,0°C -> float

Implizite Typumwandlung

[Bearbeiten]

Diese Regeln wurden so aufgestellt, dass dabei stets ein Datentyp in einen anderen Datentyp mit höherem Rang umgewandelt wird (Rangordnung: long double, double, float, long long, long, int) [C11 6.3.1.8] .

Nach [Harbison: S. 199]
Regel/
Priorität
If either operand
has Type
And the other operand
has Type
Converts both to
1long doubleany real typelong double
2doubleany real typedouble
3floatany real typefloat
4any unsigned typeany unsigned typeThe unsigned type
with the greater rank
5any signed typeany signed typeThe signed type
with the greater rank
6any unsigned typea signed type of greater rank that can
represent all vaues of the unsigned type
The signed type
7any unsigned typea signed type of greater rank that cannot
represent all values ot the unsigned type
The unsigned version
the the signed type
8any other typeany other typeNo conversion

Ergänzend gilt das Regelwerk zu Integer Promotion [C11 6.3.1.1], welche Datentypen kleiner als signed int zu int und kleiner als unsigned int und unsigned int konvertiert (Compiler kann hiervon abweichen, wenn sichergestellt ist, dass kein Datenverlust eintritt).

Regel 6 und 7 sind der nicht klar definierten Bitbreite der ganzzahligen Datentypen geschuldet und versuchen, Konvertierungsverluste zu vermeiden. Sie lesen sich zunächst kryptisch, lassen sich aber einfach am folgenden Beispiel erklären:

Operand 1: unsigned int (hier 32-Bit)Operand 2: long (hier 32-Bit / 64-Bit)
  • Im Fall, dass der Datentyp long 64-Bit breit ist, kann dieser problemlos die vorzeichenlose 32-Bit Zahl ohne Konvertierungsverluste repräsentieren, so dass der Zieltype signed long ist (Regel 6)
  • Im Fall, dass der Datentyp long 32-Bit breit ist, kann dieser mit seinen Wertebereich von -2.147.483.648 bis +2.147.483.647 nicht den vorzeichenlosen Zahlenbereich von 0…4.294.967.295 darstellen. In diesem Fall hat der unsigned Datentyp einen höheren Rang, so dass als Zieltyp unsigend long gewählt wird(Regel 7)

Regel 7 bedeutet die Gefahr eines Konvertierungsverlustes, dessen man sich bewusst sein sollte! Diese tritt insb. dann in Kraft, wenn ein Operand vom Typ 'unsigned long long' ist (64-Bit).

Für die Konvertierung von Pointer, Arrays, Strukturen, Unions zu anderen Datentypen, als sich selbst gilt Regel 8. In diesem Fall gibt der Compiler zumeist einen Error, in wenigen Ausnahmefällen eine Warning aus:

char *string; //Pointerint arr[3]; //Arraystruct {int x,y;} var1; //Strukturenunion {int x,y;} var2; //Unionvar1 = var2; //Error Incompatible Typesstring=arr; //Error Incompatible Typesarr=var1; //Error Assignment to expression with array type //(=incompatible Types)string=var2; //Error Incompatbiles Types

Öffnen im Compiler Explorer

Beispiele von impliziten Typumwandlungen:

char varc=100;short vars=100;int vari=100;vars=varc + vars;//Wird aufgrund der impliziten Regel wie folgt umgesetztvars=(short)((int)varc+(int)vars);vari=5.0*(int)sin(vars);//Wird aufgrund der impliziten Regel wie folgt umgesetztvari=(int)(5.0*(double)(int)sin((double)vars));//Hinweis: da sin() nur Werte im Bereich -1…0…+1 zurückgibt kommen//als Werte für a hier nur 5, 0 und -5 in Frage!

Öffnen im Compiler Explorer

Hinweis:

  • Im CompilerExplorer können sie sich die impliziten Typumwandlungen anzeigen lassen. Dazu gehen sie bitte wie folgt vor:
  • Im Source-Fenster mit '+Add new' ein Compiler Fenster öffnen
  • Im Compiler-Fenster mit '+Add new' ein 'GCC Tree/RTL' Fenster öffnen
  • Im GCC Tree/RTL-Fenster unter 'Select a pass…' 'original tree' auswählen


Um implizite Typumwandlung besser erkennen zu können, wird oftmals bei Variablennamen die 'Ungarische Notation' angewendet (siehe Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (9)Ungarische Notation). Aufgrund der besonderen Namensgebung kann der Programmierer ohne großen Aufwand frühzeitig mögliche Typkonflikte erkennen. Der Variablenname setzt sich wie folgt zusammen:

{Präfix}{Datentyp}{Bezeichner}
Präfix: p->Pointer h->Handle i->index c->count f->flag rg->Array
Datentyp: ch->Character st->string w->word b->byte…
Bezeichner: Zum Binden der Variable an eine konkrete Aufgabe (keine Unterstriche).
Bezeichner ist optional, wenn aus Präfix und Datentyp die 'Aufgabe' der Variable direkt sichtbar ist

Beispiele:

char rgchtemp[10]; //Array (Range) vom Datentyp characterint ich; //Index zum Adressieren eines Arrays vom Datentyp //character

Explizite Typumwandlung

[Bearbeiten]

Programmierer erzwingt durch explizite Typumwandlung (auch CASTen genannt) eine Umwandlung eines Datentyps in einen anderen. Dazu wird der Zieldatentyp in runden Klammern vor Quelldatentype geschrieben:

short a=4;double b=(double)(a+1); //Das Ergebnis von (a+1) wird nach double gecastet //(Addition erfolgt auf Basis des Datentyps. //integer)double c=(double)a+1; //a wird nach double gecastet, so dass nachfolgende //Addition auf Basis von double basiert.

Öffnen im Compiler Explorer

In der Rangfolge der Operatoren steht der Cast-Operator unterhalb von bspw. Funktionsaufrufen, Arrayzugriffen aber auch der Dereferenzierung:

PrioritätSymbolAssoziativitätBedeutung
15.........
14

++/-- (Präfix)
+/- (Vorzeichen)
!/~
&
*
(TYP)
...

R-L

Präfix-Inkrement/Dekrement
Vorzeichen
Logisches/Bitweises Nicht
Adresse
Zeigerdereferenzierung
Typumwandlung (Explizites Cast)
...

13
  • /%
L-RMultiplikation/Division/Modulo
12.........

Im Zweifel gilt auch hier, den zu wandelnden Typ ergänzend zu klammern.

Hinweis:

  • Bedenke, dass die explizite Typumwandlung so aufgestellt sein sollte, dass der Zieldatentyp dem notwendigen Datentyp entspricht. Andernfalls wendet der Compiler ergänzend eine implizierte Typumwandlung an:
int a;double b=4.7;short c1= (long)( (float)a+b );//b ist vom Typ double, so dass a nach dem Cast auf float // implizit vom Compiler auf double gecastet wird//c ist vom Typ short, so dass das Ergebnis der Addtion nach dem expliziten //Cast auf long auf short gecastet wird.//Nach Anwendung der impliziten Cast sieht der Ausdruck wie folgt aus:short c2=(short)(long)( (double)(float)a+4.7);

Öffnen im Compiler Explorer

Mittels expliziter Typumwandlung können Ganzzahl nach Gleitkommazahlen (und andersherum) und Zeiger in einen anderen Zeiger und auf andere Datentypen gewandelt werden. Eine Konvertierung von Arrays, Strukturen, Unions zu anderen Datentypen als sich selbst ist weiterhin nicht möglich:

char *string; //Pointerint arr[3]; //Arraystruct stru {int x,y;} var1; //Strukturenunion unio {int x,y;} var2; //Unionvar1 = (struct stru) var2; //Error Conversion to non-scalar typestring=(char *)arr; //OKarr=(int *)var1; //Error Assignment to expression with array typestring=(char *)var2; //Error cannot convert to pointer type

Öffnen im Compiler Explorer

Bei den möglichen Konvertierungen sollte Folgendes berücksichtigt werden:

  • Vorzeichenlose GanzzahlVorzeichenlose Ganzzahl: Wenn der Zieldatentyp größer ist, wird die Zahl durch führende '0' erweitert. Wenn der Zieldatentyp kleiner ist, werde die zu vielen Bitstellen abgeschnitten/verworfen (ggf. Datenverlust).
  • Vorzeichenbehaftete GanzzahlVorzeichenbehaftete Ganzzahl: Wenn der Zieldatentyp größer ist, wird vorzeichenrichtig erweitertet (d.h. das Auffüllen erfolgt auf Basis des Vorzeichens). Wenn der Zieldatentyp kleiner ist, werden auch hier die zu vielen Bits abgeschnitten/verworfen (Vorsicht: Negative Zahlen können dabei in positive Zahlen gewandelt werden)
  • GleitkommazahlGleitkommazahl: Hier wird vereinfacht ausgedrückt sowohl die Mantisse als auch der Exponent einzeln kopiert. Wenn die Zielmantisse kleiner ist, gehen 'Nachkommastellen' verloren. Wenn der Quellexponent einen größeren Wert beinhaltet, als der Zielexponent 'aufnehmen' kann, so wird die Zahl auf Unendlich gesetzt. Umgedreht, wenn der Quellexponent kleiner ist, als der Zielexponent 'aufnehmen' kann, so wird die Zahl auf 0 gesetzt
  • GanzzahlGleitkommazahl: Hier wird, vereinfacht ausgedrückt, die Ganzzahl in die Mantisse kopiert. Um Datenverluste zu vermeiden, sollte die Bitbreite der Zielmantisse größer gleich der Bitbreite der Ganzzahl sein
  • GleitkommazahlGanzzahl: Im Wesentlichen wird hier die Mantisse übernommen, so dass hier die Bitbreite des Zieldatentyps mindestens der Bitbreite der Mantisse sein sollte
  • ZeigerGanzzahl: Die Konvertierung ist möglich. Da bei 64-Bit Systemen der Zeiger 64-Bit und Integer 32-Bit breit ist, meckert ggf. der Compiler. Für solche Fälle existieren die Datentypen 'intptr_t' und 'uintptr_t', über welche sichergestellt ist, dass ein Zeiger in einen Ganzzahldatentyp gespeichert werden kann!
  • ZeigerGleitkommazahl: Diese Konvertierung ist nicht möglich!
  • Struktur/UnionGanzzahl/Gleitkommazahl: Diese Konvertierung ist nicht möglich. Es können nur einzelnen Strukturelemente konvertiert werden, sofern diese vom Typ Ganzzahl/Gleitkommazahl sind
  • ArrayGanzzahl/Gleitkommazahl: Der Arrayname entspricht einen Zeiger, so dass hier ein Zeiger in eine Ganzzahl oder umgedreht konvertiert wird (siehe Zeiger ↔ Ganzzahl)

In C++ gibt es weitere CAST-Operatoren, auf welche hier derzeit noch nicht weiter eingegangen wird!

  • Static_cast (Entspricht dem Expliziten Cast)
  • Const_cast
  • Dynamic_cast
  • Reinterpret_cast

Struktur/Verbundtyp

[Bearbeiten]

Nach dem Wikipedia Artikel Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (10)Verbund (Datentyp) ist ein Verbund (englisch object composition) ist ein Datentyp, der aus einem oder mehreren Datentypen zusammengesetzt wurde. Die Komponenten können wiederum Verbünde enthalten, wodurch auch komplexe Datenstrukturen definiert werden können.
Für jedes Strukturelement wird Speicher reserviert. Alle Strukturelemente liegen hintereinander im Speicher.

Syntax: struct StrukturnameOpt {Datentype Strukturelementname; … }OPT VariablenlisteOpt;

Ein Verbund entspricht einer Java Klasse mit dem Unterschied zu C (nicht C++) , dass ein Verbund keine Methoden beinhaltet und keine Zugriffsbeschränkung für die Attribute gesetzt werden können.

Der Syntax erlaubt es, gleichermaßen einen Datentyp (über Strukturname) zu definieren und Variablen von diesem Datentyp (über Variablenliste) anzulegen. Da beide Elemente optional sind, ergeben sich diverse Kombinationsmöglichkeiten:

  • Nur Strukturname → Definition eines neuen Datentyps
struct xyz1 {int x; int y,z;}; //Definition eines neuen Datentypsstruct xyz1 var1; //Definition einer Variablen dieses //Datentypsvar1.x=7; //Zugriff auf ein Strukturelement

Öffnen im Compiler Explorer

C: Der Strukturname ist nur mit dem vorangestellten struct gültig. Der Strukturname stellt somit einen eigenen Namensraum, getrennt von dem Namensraum der Variablen/Funktionen, dar. Daher kann ein Strukturname identisch zu einem Variablennamen sein:
struct xyz2 {int x,y,z;}; //Definition des Datentyps 'struct xyz2'struct xyz2 xyz2; //Definition der Variablen 'xyz2' auf //Basis des Datentyps 'struct xyz2'xyz2 var2; //Compilerfehler, zur Nutzung des Datentyps //'struct xyz2' muss struct vorangestellt  //werden!

Öffnen im Compiler Explorer

C++: Der Strukturname ist sowohl mit als auch ohne dem vorangestellten struct gültig. Auch hier stellt der Strukturname einen eigenen Namensraum dar, der jedoch eine niedrigere Priorität als der der Variablen hat:
struct xyz3 {int x,y,z;};xyz3 xyz3={1,1,1}; //'xyz3' beschreibt hier den Datentypenxyz3.x=1; //und nach Definition einer Variable die Variable!xyz3 var2; //Fehler, xyz beschreibt hier die Variable

Öffnen im Compiler Explorer

  • Nur Variablenliste → Definition einer/mehrerer Variablen von einem 'unnamed' Datentyp
struct { int x,y,z;} xyz,arr[3],*ptr=&xyz; //Definition von Variablen des Datentyps  //struct {...}. Da dieser Datentyp nicht benannt //wurde, kann im späteren keine weitere Variable //von diesem Datentyp angelegt werden. xyz.x =8; //Nutzung der Variablenarr[1].y =9;ptr->x = arr[1].y;

Öffnen im Compiler Explorer

Angewendet wird die Schreibweise gerne, wenn eine Struktur innerhalb einer Struktur definiert wird und die innere Struktur zur besseren Strukturierung dient:
struct aussen { //Äußere Struktur struct { //Innere Struktur int a,b,c; } anwendung1; struct { //Innere Struktur int x,y,z; } anwendung2;};struct aussen var;var.anwendung1.a=10; //Nutzung der Variablenvar.anwendung2.y=12;

Öffnen im Compiler Explorer

  • Strukturname und Variablenliste → Definition eines Datentyps und Definition von Variablen. D.h. es können im Nachhinein weitere Variablen von diesem Datentyp angelegt und die hier angelegten Variablen genutzt werden.
struct xyz{ //Definition eines Datentyps int x,y,z;} var1,*ptr1; //und Definition von Variablen dieses Datentypsstruct xyz var2,*ptr2; //Definition weiterer Variablen dieses Datentypsvar1.x=var2.y; //Nutzung der Variablen

Öffnen im Compiler Explorer

  • Kein Strukturname und keine Variablenliste → "Anonymous Struct", d.h. Definition eines 'unnamed' Datentyps, von der ergänzend keine Variable angelegt wird. Anonymus Structs sind nur innerhalb von Strukturen erlaubt/einsetzbar. Hier dienen sie der besseren Strukturierung und ersparen dem Programmierer die Benennung des normalerweise notwendigen Strukturelementes:
struct außen { int a; struct { int b,c,d; } anwendung1; //'Normale' innere Struktur struct { int x,y,z; }; //Anonyme Struct als innere Struktur} var;var.a=4711; //Nutzung der Variablenvar.anwendung1.b=1;//Bei Zugriff auf anonyme Struktur ist die Benennung //des Strukturelementes notwendigvar.x=1; //'Einfacherer' Zugriff auf inneres Element bei  //'anonymous struct'

Öffnen im Compiler Explorer

Hinweise:

Strukturelement

[Bearbeiten]

Innerhalb der Struktur können beliebige Strukturelemente von beliebigen Datentypen angelegt werden. Einzige Voraussetzung, der Datentyp des anzulegenden Strukturelementes muss zuvor definiert worden sein. Ergänzend kann der neue Datentyp auch in der Struktur selbst definiert werden, sofern mit der Definition des Datentyps auch eine Variable angelegt wird:

struct abc { int a,b,c;}; //Definition des Datentyps struct abcstruct xyz { //Definition des Datentyps struct xyz int x,y,z; //Strukturelement vom Type int struct abc abc; //Strukturelement vom Type struct abc struct def { //Definition eines neuen Datentyps innerhalb int d,e,f; //der Struktur bei gleichzeitigen Anlegen zweier } def,geh; //Strukturelementen von diesem neuen Datentyp struct uvw { int u,v,w; //Warning, reine Datentypdefinition innerhalb }; //einer Struktur nicht möglich};struct def abc; //Innere Datentypbeschreibung auch //außerhalb der Struktur nutzbar

Öffnen im Compiler Explorer

Zugriff auf Strukturelemente

[Bearbeiten]

Die Programmiersprache C unterscheidet zwei 'Zugriffsarten' auf die Strukturelemente, abhängig vom zugrundeliegenden Datentyp:

  • Handelt es sich beim zugrundeliegenden Datentyp um eine (struct)Variablen, so erfolgt der Zugriff über den Punkt-Operator .:
struct { int a,b,c;} abc;abc.a=4711; //abc ist eine Variable einer Strukturabc.b=abc.c;

Öffnen im Compiler Explorer

  • Handelt es sich beim zugrundeliegenden Datentyp um einen Zeiger auf eine (struct)Variable, so erfolgt der Zugriff über den Zeiger-Operator ->:
struct xyz { int x,y,z;};struct xyz var1; //Variable von Datentyp struct xyz;struct xyz *ptr1; //Zeiger auf eine Struktur vom Datentyp struct xyzptr1=&var1; //Initialisierung des Zeigers  //(mit der Adresse der Variablen var1)var1.x= 7; //Zugriff auf das Strukturelement x über die Variable var1ptr1->x=7; //Zugriff auf das Strukturelement x über den Zeiger ptr1(*ptr1).x=7; //Der Zeiger-Operator entspricht dem Zugriff auf ein //dereferenziertes Strukturelement

Öffnen im Compiler Explorer

Werden innerhalb von Strukturen weitere Strukturen und Zeiger auf Strukturen angelegt, so müssen diese Regeln auf jedes innere Strukturelement einzeln angewendet werden:

struct abc { //Äußere Struktur int a,b,c; struct xyz { int x,y,z; } xyz; //Inneres Strukturelement xyz vom Datentyp struct xyz struct { char str[10]; } strstr; //Inneres Strukturelement strstr vom Datentyp  //struct strstr struct xyz *ptr; //Inneres Strukturelement ptr vom Datentyp Zeiger  //auf struct xyz} var2,*ptr;var2.a=8; //var2 vom Datentyp 'struct abc'. Zugriff über '.'var2.xyz.x=1; //xyz vom Datentyp 'struct xyz'. Zugriff über '.'var2.strstr.str[0]='a';//strstr vom anonymes Struct. Zugriff über '.'ptr=&var2; //ptr mit einer Adresse initialisierenvar2.ptr = &var2.xyz; //Strukturelement ptr mit einer Adresse initialisierenptr->a='a'; //ptr vom Datentyp 'Zeiger auf struct abc' //Zugriff über '->' var2.ptr->x=3; //ptr vom Datentyp 'Zeiger auf struct xyz'. //Zugriff über '->'ptr->xyz.x=3;ptr->ptr->x=3;

Öffnen im Compiler Explorer

Initialisierung von Strukturen

[Bearbeiten]

Mit dem Anlegen einer Strukturvariablen kann diese auch initialisiert werden. Eine 'Vorbelegung' der Strukturelemente bei der Definition der Struktur ist in C nicht möglich.
Die Initialisierung erfolgt über eine Initialisierungsliste (siehe Kap Grundlagen:Initialisierungsliste / Compound Literal). Innerhalb der Initialisierungsliste stehen die Initialisierungswerte entweder in der Reihenfolge der Datentypdefinition oder werden explizit mit dem ‘.‘ Operator angesprochen (designated initializers). Werden einzelne Strukturelemente ausgelassen, so werden diese mit 0 initialisiert:

struct abc{ int a,b; int c=3; //KO: Kein Initialisierungswert von Strukturelementen möglich char str[10];};struct abc v1={1,2,"hallo"}; //Alle Strukturelemente werden initialisiertstruct abc v2={ 1,2 }; //Initialisierung der Strukturlemente a und b //Rest wird mit 0 initialisiertstruct abc v3={ .b=3}; //Initialisierung einzelner/designated Strukturele. //Rest wird mit 0 initialisiert struct abc v4={.a=strlen(v1.str)}; //Initialisierungswert ergibt //sich erst zur Laufzeit. //Nur als lokale Variable möglich!struct abc v5={.b=1,.a=2,.b=12}; //Nur C: Reihenfolge und Doppelbenennung //der Strukturelemente egal/möglichstruct abc v6={}; //Alle Strukturelemente mit 0 initialisieren

Öffnen im Compiler Explorer

Eine Initialisierung einer Struktur über nachfolgende Art bewirkt etwas anderes, als erwartet:

struct abc { int a,b,c; };struct abc var={var.b=12,var.c=3};

Öffnen im Compiler Explorer

Innerhalb der Initialisierungsliste wird über 'var.b=12' und 'var.c=3' die zu diesem Zeitpunkt noch nicht initialisierte Variable var die Elemente b und c initialisiert. Mit dem Werten der Initialisierungsliste (hier {12,3}) wird nachfolgend die Variable erneut initialisiert und 12 dem Strukturelement a und 3 dem Strukturelement b zugewiesen. Abhängig vom Compiler ist das Strukturelement c entweder 3 oder 0.

Bei verschachtelten Strukturen muss entsprechend obiger Aussage für jede innere Struktur eine eigene Initialisierungsliste erstellt werden. Die inneren Initialisierungslisten können entfallen, was jedoch nicht empfohlen wird und ergänzend vom Compiler bei '-Wall' als Warning angemerkt wird:

struct abc { int a,b; struct xy { int x,y; } xy; struct xy arr[2];};struct abc v1={ //Initialisierung über Reihenfolge 1,2, {3,4}, { {5,6},{7,8}}};struct abc v2={ //Initialisierung über 'designated initializers' .arr={{.x=5,.y=6},{7,8}}, .xy={3,4}, .a=1,.b=2};struct abc v3={ //Fehlende innere Initialisierungsliste 1,2, 3,4, 5,6,7,8};

Öffnen im Compiler Explorer

Zuweisen/Kopieren von Strukturen

[Bearbeiten]

Strukturen werden über den Namen als 'ganzes' angesprochen, so dass eine Zuweisung als 'ganzes' möglich ist. Bei bspw. einer 12-Byte großen Struktur werden bei Nutzung der Variable die gesamten 12-Byte gelesen/geändert!

struct xyz {int x,y,z;} var1,var2;var1=var2; //12-Bytes kopiert//--> entspricht memcpy(&var1,&var2,sizeof(xyz));

Die Zuweisung als 'ganzes' gilt auch bei der Parameterübergabe und -rückgabe von Funktionen:

struct xyz {int x,y,z;};//Funktionsdefinitionstruct xyz function(struct xyz par) {  return (struct xyz){par.z,par.y,par.x};}//Funktionsaufrufstruct xyz var=function((struct xyz){1,2,3});//Mit dem Aufruf der Funktion werden 12-Bytes in die Variable par kopiert//Mit dem Ende der Funktion werden 12-Bytes in die Variable var kopiert

Bedenke, dass bei großen Strukturen das Kopieren Rechenzeit benötigt und folglich vermieden werden sollte! Alternativ sollten Zeiger auf Strukturen über- und zurückgeben werden.

Über Compound Literal (siehe Kap. Grundlagen:Initialisierungsliste / Compound Literal) kann einer Strukturvariablen als 'ganzes' einen neuen Wert zugewiesen werden:

struct abc {int a,b,c;};var1={.a=7}; //KO, Initialisierungsliste kann hier nicht //angewendet werdenvar1=(struct abc){.a=7}; //Wertezuweisung über Compound Literal möglich

Formel gesehen entspricht das Compound Literal einer 'unnamed Variable', die als erstes angelegt und initialisiert wird. Der Inhalt dieser Variablen wird im Anschluss der eigentlichen Variable zugewiesen!

Prototyp / Deklaration einer Struktur

[Bearbeiten]

Soll der Datentyp einer Struktur genutzt werden, der erst an weiter hinten liegenden Stellen im Programm definiert wird, so ist wie bei Variablen/Funktionen ein Prototyp/Deklaration notwendig:

struct abc; //Prototyp der Struktur abc...struct abc { int a,b,c;}; //Definition der Struktur abc

Die Deklaration sagt einzig aus, dass die Struktur später definiert wird, beinhaltet aber nicht die Strukturelemente. Folglich kann auf Basis dieses Prototyps keine Variable definiert werden, sondern einzig ein Zeiger auf solche eine Struktur:

struct xyz; //Deklaration / Prototyp des Datentyps // d.h. keine Benennung der Strukturelementestruct xyz var1; //KO, es kann auf Basis der Deklaration keine // Variable angelegt werdenstruct xyz *pttr1; //OK, von einer Strukturdeklaration kann ein Zeiger // angelegt werden!

Öffnen im Compiler Explorer

Anwendung:

  • Struktur, welche auf sich selbst verweist (Verkettete Liste)
struct vl { //Entspricht gleichermaßen einem Prototyp, so dass //innerhalb dieser Struktur dieser Datentyp zum //Anlegen eines Strukturelementes genutzt werden kann struct vl *next; //Zeiger auf das nächste Element char daten[100];};

Öffnen im Compiler Explorer

  • Entsprechend der objektorientierten Programmierung, wenn alle Attribute der Klasse private sind:
class.h
struct class; //Prototyp für Struktur //so dass Nutzer dieser //Struktur ein Zeiger auf diese anlegen,//aber nicht auf die Strukturelemente//zugreifen können.//Prototyp der public Methodenvoid konstruktor(struct class ** me);
main.cclass.c
#include "class.h"int main(int argc, char*argv[]){ struct class *obj1; konstruktor(&obj1); .. //Kein Zugriff auf  //Strukturelemente möglich return 0;}
#include "class.h"struct class { int attr1; int attr2;};void konstruktor(struct class ** me){ struct class *this; this=(struct class *) malloc(sizeof(struct class)); this->attr1=10; *me=this;}

Öffnen im Compiler Explorer

Vergleichen von Strukturen

[Bearbeiten]

Ein Vergleich von Strukturen über den direkten Weg ist nicht möglich. Vielmehr müssen die Strukturelemente händisch verglichen werden:

struct xyz {int inx,y,z;} var1,var2;if(var1 == var2) //KO, ein Vergleich von Strukturvariablen ist nicht möglich//Manueller Vergleich über den Vergleich der Strukturelementeif(var1.x == var2.x && var1.y == var2.y && var1.z == var2.z)

Öffnen im Compiler Explorer

Hinweis

  • Bei Rechnerarchitekturen mit nicht ausgerichteter Speicherausrichtung (Alignment) kann ergänzend ein Vergleich über memcmp() erfolgen

Speicherplatzbedarf einer Struktur

[Bearbeiten]

Für jedes Strukturelement wird Speicher entsprechend der Größe des Datentyps reserviert. Der Speicherplatzbedarf der Gesamtstruktur ergibt sich aus der Summe der Strukturelemente:

struct xyz{ int x; //Strukturelement belegt 4 Byte Speicher int y; //Strukturelement belegt 4 Byte Speicher int z; //Strukturelement belegt 4 Byte Speicher}var1; //Speicherplatz der Struktur = 4+4+4 = 12 Bytesizeof(var1) //ergibt 12sizeof(struct xyz) //ergibt 12

Öffnen im Compiler Explorer

Die Ermittlung der tatsächlichen Speichergröße einer Struktur erfolgt über den sizeof-Operator:

struct xyz{int x,y,z;} a;sizeof(a); //=12 OKsizeof(a.x); //=4 OKsizeof(struct xyz); //=12 OKsizeof(struct xyz.x); // KO//Ist dies dennoch notwendig, so kann dies über den Umweg eines Zeigers//erfolgen:sizeof(((struct xyz *)0)->x); //=4 OK

Öffnen im Compiler Explorer

Hinweis:

  • Bei Rechnerarchitekturen mit ausgerichteter Speicherausrichtung (Alignment) (siehe Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (11)Speicherausrichtung) kann der tatsächliche Speicherbedarf größer sein! Hier fügt der Compiler ggf. zwischen den Strukturelemente Füllbytes (Padding Bytes) ein
  • Die Reihenfolge der Strukturelemente wird vom Compiler nicht geändert. D.h. die Zuordnung der Speicherstellen zu den Strukturelementen erfolgt in der Definitionsreihenfolge

Interne Organisation

[Bearbeiten]

Compilerintern werden die Strukturelemente über einen Offset dargestellt. Das erste Strukturelement bekommt dabei immer den Offset 0 zugewiesen. Das nachfolgende Element bekommt als Offset die Größe des Datentyps des vorherigen Elementes zugewiesen. Usw..
Der Compiler setzt den Zugriff auf ein Strukturelement einer (Struktur)Variablen so um, dass er zunächst die Startadresse der Variablen holt und zu dieser den Offset des Strukturelementes addiert. Die Anzahl der zu lesenden/schreiben Bytes ergibt sich aus dem Datentyp des Strukturelementes:

struct xyz { //Das Anlegen der Variable var bedeutet, int x,y,z; //12Byte Speicher zu reservieren.} var1; //(Beispielhaft soll var1 die Speicheradressen  //0x100..0x10B belegen). Der Zugriff auf //die Variable erfolgt immer über die Startadressevar1.x=7; //Zugriff auf Speicheradresse 0x100+0 (Offset)var1.y=8; //Zugriff auf Speicheradresse 0x100+4 (Offset)var1.z=9; //Zugriff auf Speicheradresse 0x100+8 (Offset)

Öffnen im Compiler Explorer


Der Offset eines Strukturelementes kann mit dem offsetof-Operator ermittelt werden:

Syntax: offsetof(type,Strukturelement)

Wie beim sizeof-Operator gilt der offsetof-Operator als Konstantenausdruck und der Rückgabedatentyp ist size_t. Zur Nutzung des Operators muss die Header-Datei 'stddef.h' inkludiert werden:

#include <stddef.h> //Zur Nutzung des OffsetOf Operatorsstruct xyz {int x,y,z;} var1;offsetof(struct xyz,z) --> 8 offsetof(var1,z) --> KO: Es wird ein Datentyp und  keine Variable erwartet

Öffnen im Compiler Explorer

Explizites Cast

[Bearbeiten]

Eine Struktur kann nicht in einen anderen Datentyp und damit auch nicht in einen anderen Strukturdatentyp mit identischen Strukturelementen konvertiert werden:

struct xyz {int x,y,z;} xyz;struct abc {int a,b,c;} abc;int var;xyz=abc; //KO, Datentyp struct xyz!= struct abcxyz=(struct xyz)abc; //KO, siehe zuvorint neu1 = (int) xyz; //KOint neu2 = (int) xyz.x; //OK (Das Strukturelement x ist vom Datentyp int)struct xyz neu2 = (struct xyz)3; //KO

Öffnen im Compiler Explorer

Incomplete Array

[Bearbeiten]

Das letzte Element einer Struktur kann ein Array ohne Angabe einer Dimensionsgröße sein (Incomplete Array/Flexible Array Member). Bei der Definition einer Variablen von solch einem Datentyp wird in der Tat kein Speicher für das letzte Element reserviert, so dass dies 'händisch' erfolgen muss. Auf das Array kann ungeachtet dessen unproblematisch zugegriffen werden:

struct vl { struct vl *next;  char data[]; //incomplete Array / flexible Array Member};struct vl a; //Es wird nur Speicher für den next Zeiger reservierta.data[0]='7'; //Syntax OK, jedoch erzeugt dies ein Laufzeitfehler, //da kein Speicherplatz für das Incomplete Array reserviert //wurde

Öffnen im Compiler Explorer

Die Speicherplatzreservierung für das incomplete Array kann auf zwei verschiedene Arten erfolgen:

  • Speicherplatzreservierung für das Incomplete Array über Variableninitialisierung (nur GCC-C und nur im Falle einer globalen Variablen)
struct vl ele = { .next = NULL, .data = {32, 31, 30}}; //Vorsicht, sizeof(ele) gibt dennoch nur 4/8 zurück

Öffnen im Compiler Explorer

  • Speicherplatzreservierung für das Incomplete Array über malloc
struct vl *b=malloc(sizeof(struct vl)+ //Größe der Struktur sizeof(char[3] )); //Größe des Arraysb->data[1]='7'; //OK, da mit dem Anlegen von b Speicher für 2  //data Element reserviert wurde

Öffnen im Compiler Explorer


Das Incomplete Array bietet sich überall dort an, wo Daten gespeichert werden sollen, deren Größe sich erst zur Laufzeit ergibt.

Anwendung

[Bearbeiten]

Die Nutzung des Datentyps struct empfiehlt sich an vielen Stellen:

  • Zusammengehörende Daten zu Kapseln (entsprechend der objektorientierten Programmierung
  • Verkette Listen
  • Aufgrund der Typsicherheit zur Darstellung von sicherheitskritischen Aufgaben

Siehe Übungsbereich!

C++

[Bearbeiten]

Der wesentliche Syntax von Strukturen wurde in C++ übernommen. D.h.:

  • das abschließende Semikolon
  • der Zugriff auf die Strukturelemente
  • Speicherplatzreservierung
  • die Definition von Variablen mit der Definition des Datentyps
  • ...

Ergänzend wurde in C++ der Datentyp class eingeführt, der weitestgehend identisch zu struct ist. Geändert/Hinzugefügt wurden folgende Sachverhalte:

  • Classen/Strukturen können Methoden haben
  • Operatoren (Zuweisungsoperator, Addition, ...) können überladen werden
  • Alle Strukturelemente (und Methoden) einer struct sind per default public
  • Alle Attribute (und Methoden) einer Class sind per default private
  • Zur Nutzung des Datentyps ist das führende struct nicht notwendig (dies bedingt dann auch, dass der Strukturdatentyp kein separater Namensraum ist)
struct xyz { int x,y,z;};xyz var_xyz;
  • Initialisierungswerte für Strukturelemente und Klassenattribute bei der Datentypbeschreibung angegeben werden können:
struct xyz { int x=3; int y=1; int z;};xyz var={7}; //var.x=7 var.y=1 var.z=0

Öffnen im Compiler Explorer

  • In C++ kann eine Struktur ebenfalls über 'designated initializers' initialisiert werden. Jedoch muss die Reihenfolge der Initialisierungselement identisch zur Datentypdefinition sein. Auch ist keine Doppeltbenennung erlaubt:
struct xyz { int x,y,z;};xyz var1={.x=1, .y=2, .z=3}; //OK, Reihenfolge identischxyz var2={.y=1, .z=3}; //OK, Reihenfolge identischxyz var3={.y=1, .x=0}; //KO, Reihenfolge nicht identischxyz var4={.x=1, .x=2}; //KO, keine Doppeltbenennung möglich

Öffnen im Compiler Explorer

Union

[Bearbeiten]

Ähnlich wie eine Struktur ist ein Union ein Datentyp, der aus einem oder mehreren Datentypen zusammengesetzt wird. Bei sog. Union beginnen jedoch alle Komponenten nicht wie bei Strukturen an nacheinander folgenden Speicheradressen, sondern an der identischen Speicheradresse, d.h. ihre Speicherbereiche überlappen sich ganz oder zumindest teilweise. Eine Union kann folglich zu einem Zeitpunkt nur ein Element enthalten. Der benötigte Speicherplatz ergibt sich aus der größten Komponente.

Syntax: union UnionnameOpt {Datentyp Variablenname; …}Opt VariablenlisteOpt;

Ein Schutz/Zugriffssteuerung der Unionelemente ist nicht vorhanden. Es kann in beliebiger Reihenfolge auf die einzelnen Elemente zugegriffen werden.

Die Anwendung ist identisch wie bei Strukturen, so dass im Folgenden nur die Abweichungen zu den Strukturen beschrieben werden.

Speicherplatzbedarf und interne Struktur einer Union

[Bearbeiten]

Die Größe der Union ergibt sich aus dem größtem Unionelement. Alle Unionelemente überlappen sich, so dass der Offset zum Basiselement 0 ist:

union abc { char a; short b; int c;} abc;printf("Größe: %zu\n",sizeof (union abc)); //-> 4printf("Offset a:%zu\n",offsetof(union abc,a)); //-> 0printf("Offset b:%zu\n",offsetof(union abc,b)); //-> 0printf("Offset c:%zu\n",offsetof(union abc,c)); //-> 0

Öffnen im Compiler Explorer

Wird ein kleineres Unionelement belegt und im Anschluss ein größeres Unionelement gelesen, so ist der Inhalt der durch das kleinere Element nicht beschriebenen Speicherstellen undefiniert:

union abc { char a; short b; int c;} abc;abc.a=0x11;printf("%08x",abc.c);

Öffnen im Compiler Explorer

Initialisierung von Union

[Bearbeiten]

Bei einer Union kann nur ein Element initialisiert werden, d.h. die Initialisierungsliste kann nur einen Wert beinhalten. Ohne explizite Benennung des Unionelementes in der Initialisierungsliste wird das erste Element aus der Datentypbeschreibung initialisiert. Mit expliziter Benennung mittels 'designated initializers' können auch andere Elemente initialisiert werden:

union abc { char a; short b; int c;} abc; abc=3; //KO keine Initialisierungslisteabc=(union abc){3}; //OK Initialisierung des ersten Elementes aabc=(union abc){.b=3}; //OK Initialisierung des Elementes b abc=(union abc){3,4}; //KO nur ein Initialisiuerngswert erlaubt

Öffnen im Compiler Explorer

Anwendung

[Bearbeiten]

Mittels des Datentyps union können diverse Anwendungsfälle abgedeckt werden:

  • Über Union lassen sich größere Datentypen in kleinere Datentypen unterteilen, um z.B. einzelne Bytes zu extrahieren:
union floatu { float var; //Float ist 4-Byte groß unsigned int hex; //Integer ist 4-Byte groß char byte[4]; //Ohne Worte};union floatu var={1.234};printf("%f\n",var.var); //Darstellung des Float-Wertesprintf("%x\n",var.hex); //Darstellung des Float-Wertes 'BinäreZahl'printf("%hhx %hhx %hhx %hhx\n", //Darstellung der einzelnen Bytes var.byte[0],var.byte[1],var.byte[2],var.byte[3]);union longlong { long long ll; //Long Long ist 8-Byte groß int i[2]; //Ohne Worte short s[4]; char c[8];};union longlong var2={0x123456789ABCDEFULL};printf("%llx\n",var2.ll); printf("%x %x\n",var2.i[0],var2.i[1]);//Vorsicht: Die Aufteilung der Bytes ist von der Rechnerarchitektur//(Endianes) und von der Ausrichtung durch den Compiler abhängig

Öffnen im Compiler Explorer

  • Ein weiterer Anwendungsfall ergibt sich, wenn in einer Struktur unterschiedliche Datensätze gespeichert werden sollen (siehe Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (12)Tagged Union):
struct set { char *index; char *value;};struct get { char *index; char *value;};struct cli{ //Enum zur Darstellung des aktiven Unionelementes hilfreich enum {SETTER,GETTER} tag; union { //Interpretation abhängig von tag struct set set; struct get get; } ;};struct cli var={.tag=SETTER, .set.index="hallo"};if(var.tag==SETTER) printf("Set: %s",var.set.index);else printf("Get: %s",var.get.index);

Öffnen im Compiler Explorer

  • Mittels eines Tagged Union kann komfortabel ein Binärbaum realisiert werden:
//Quelle: https://github.com/Hirrolot/datatype99struct BinaryTree; //Prototypstruct BinaryTreeNode { struct BinaryTree *lhs; int x; struct BinaryTree *rhs;} BinaryTreeNode_t;struct BinaryTree { enum { Leaf, Node } tag; union { int leaf; struct BinaryTreeNode node; } data;};int sum(const struct BinaryTree *tree) { switch (tree->tag) { case Leaf: return tree->data.leaf; case Node: return sum(tree->data.node.lhs) +  tree->data.node.x +  sum(tree->data.node.rhs); } return -1; // Invalid input (no such variant).}

Öffnen im Compiler Explorer

C++

[Bearbeiten]

Die Eigenschaften des Datentyps union entsprechen den Eigenschaften des Datentyps struct in C++.

Enum/Aufzählungstyp

[Bearbeiten]

Nach dem Wikipedia Artikel Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (13)Aufzählungstyp ist ein Aufzählungstyp (englisch enumerated type) ein Datentyp für Variablen mit einer endlichen Wertemenge. Alle zulässigen Werte des Aufzählungstyps werden bei der Deklaration des Datentyps mit einem eindeutigen Namen (Identifikator) definiert, sie sind Symbole.

Syntax: enum enumnameOpt {definition-list[=expression]}Opt VariablenlisteOpt

Die Anwendung ist identisch wie bei Strukturen, so dass nachfolgend nur die Abweichungen zu den Strukturen beschrieben werden.

Datentyp von enum

[Bearbeiten]

In C entspricht der Datentyp Enum dem Datentyp int so dass Zuweisungen/Vergleiche mit Ganzzahlen möglich sind. Ganzzahloperationen funktionieren ebenso:

enum STATUS {OK,KO=5}; //Definition des Datentypsenum STATUS status; //Definition einer Variable dieses Datentypsstatus=OK;if(status == 5) status++; status=5.7;

Öffnen im Compiler Explorer

Enumelemente

[Bearbeiten]

Die Elemente der Definitionsliste sind Integerkonstanten. Diese können auch in Kombination mit anderen Datentypen genutzt werden. Auch gibt es keinen gesonderten Namensraum für Elemente der Definitionsliste. Sie 'konkurrieren' folglich mit allen Variablen- und Funktionsnamen:

enum mode {OK, KO};enum {FIRST,SECOND,LAST}; //Anonymes Enum!enum {EINS,ZWEI,LAST}; //KO, Symbolname LAST bereits vergebenenum mode var1=OK;var1=4711; //OK, enum entspricht Datentyp intint var2=KO; //OK, EnumElement entspricht Datentyp intint OK=KO; //KO, Symbolname OK bereits für EnumElement vergebenvar1=FIRST; //OK, var1 und FIRST sind zwar unterschiedlich //Enumtypen,jedoch entspricht beides dem Datentyp int

Öffnen im Compiler Explorer

Der erste Enumelement der Definitionsliste bekommt den Wert 0 zugewiesen. Folgeelemente bekommen den Wert des Vorgängerelementes +1 zugewiesen.Enumelemente können mit einer Konstanten (und Konstantenausdruck) initialisiert werden:

enum {bill=10, john=bill+2, fred=john+1} ;//Negative Werte sind möglichenum {error=-3, warning, info, ok}; //warning=-2 info=-1 …//Doppelte Wertzuweisung sind möglichenum {Ostfalia=10,Wolfenbuettel=10};//Hinter dem letzten Enumelement kann ein Komma folgenenum {test=10,};

Öffnen im Compiler Explorer

Hinweis:

  • Es empfiehlt sich, die Enumelemente in GROSSBUCHSTABEN zu schreiben. Hiermit wird gekennzeichnet, dass es sich um Konstanten und nicht um Variablen/Funktionen handelt

Anwendung

[Bearbeiten]

Die Nutzung des Datentyps enum empfiehlt sich überall dort, wo eine Fallunterscheidung notwendig ist:

  • Statusrückgabe von Funktionen:
enum STATUS {OK,MEMORY_OVERFLOW, DIVISION_BY_ZERO};enum STATUS funct() { return MEMORY_OVERFLOW;}
  • Beschreibung der Zustände und der Ereignisse eines Zustandsautomates:
enum EREIGNIS {EREIGNIS1,EREIGNIS2,EREIGNIS3};void zustandsautomat(enum EREIGNIS ereignis) { static enum {IDLE,OPERAND,OPERATOR,} zustand=IDLE; switch(zustand) { case IDLE: case OPERAND: //Compiler meckert bei fehlendem Case über OPERATOR }}

Öffnen im Compiler Explorer

C++

[Bearbeiten]

Ergänzend zu den Abweichungen bei Structs sind hier weitere Unterscheidungsmerkmale vorhanden:

  • Enums stellen einen eigenen Datentyp dar, der nicht mit int kompatible ist. Das bedingt unter anderem, dass einige Operatoren wie z.B. ++ nicht mehr auf Variablen des Datentyps enum angewendet werden. Die Enumelemente als solches sind jedoch kompatibel zum Ganzzahldatentyp:
enum Color {RED,GREN,BLUE};enum Color var1=RED;var1=4; //KOvar1=var1+1; //KOvar1++; //KOint var2=RED; //OKif(RED==2) //OK

Öffnen im Compiler Explorer

  • Auch enums können Methoden besitzen.
  • Über Operatorüberladung können z.B. nicht vorhandene Operatoren wie ++/-- ergänzt werden
  • Über unscoped/scoped Enumerations wird der 'Namensraum' gesteuert:
  • Unscoped Enumeration (Elemente der Definitionsliste stehen wie bei C im Zugriff)
Syntax: enum nameOpt: typeOPT {enumerator=constexp, …};
enum Color {RED,GREN,BLUE };Color r = RED;

Öffnen im Compiler Explorer

  • Scoped enumeration (Elemente der Definitionsliste haben einen eigenen 'Namensraum')
Syntax: enum class|struct nameOpt: typeOPT {enumerator=constexp, …};
enum class Color {RED,GREN=20,BLUE };Color r = Color::BLUE;

Öffnen im Compiler Explorer

Bitfelder

[Bearbeiten]

Nach dem Wikipediaartikel Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (14)Bitfeld bezeichnet in der Informationstechnik und Programmierung ein Bitfeld ein vorzeichenloses Ganzzahldatentyp, in dem einzelne Bits oder Gruppen von Bits aneinandergereiht werden. Es stellt eine Art Verbunddatentyp auf Bit-Ebene dar. Im Gegensatz dazu steht der primitive Datentyp, bei dem der Wert aus allen Stellen gemeinsam gebildet wird.

Der Syntax entspricht dem struct-Syntax, mit der Ergänzung, dass hinter den Strukturelementen noch die Bitbreite getrennt durch ein Doppelpunkt angegeben wird:

struct time { unsigned int hour: 5; //0..23 Bits 0..4 unsigned int minute: 6; //0..59 Bits 5..10 Unsigend int second: 6; //0..59 Bits 11..16} myTime;

Als mögliche Datentypen für die Strukturelemente stehen nur die Ganzzahldatentypen int, short, long , long long und char zur Verfügung. Der Datentyp eines Strukturelementes muss mindestens die Anzahl der Bits enthalten, wie diese über die Bitbreite gefordert wird.Von den Strukturelementen kann keine Adresse bestimmt werden! Auch der offsetf - Operator schlägt fehl.

Der Datentyp entspricht im Wesentlichen einem Ganzzahlzahldatentyp, so dass bei Zugriff auf eine Variable dieses Datentyps eine Ganzzahl gelesen/geschrieben wird. Bei Zugriff auf die einzelnen Strukturelemente werden die entsprechenden Bits dieser Ganzzahlvariablen maskiert, so dass nur die betroffenen Bits geändert werden:

struct test { unsigned char first:2; unsigned char second:4; unsigned char third:2;};struct test var={.first=1,.second=1,.third=1};//entsprichtunsigned char var1=0b01000101;var.second=1;//entspricht:var1=(var1&0b11000011) | (1<<2);var.second++;//entsprichtint second =(var1>>2)&0b00001111;second++;var1=(var1&0b11000011) | ((second&0b00001111)<<2);

Öffnen im Compiler Explorer

Anwendung

[Bearbeiten]

  • Komprimierte Speicherung der Uhrzeit (wie dies z.B. in Realtimeclocks erfolgt)
union { struct { //Datentyp zum 'Bitweisen' Zugriff unsigned int hour:5; unsigned int minute:6; unsigned int second:6; }; unsigned int hex; //Datentyp zum Zugriff aller Elemente} myTime;myTime.hour = 13;myTime.minute = 37;myTime.second = 59;printf("Zeit: %02d:%02d:%02d\n", myTime.hour,myTime.minute,myTime.second );//Alternativer/Händischer Zugriff auf die einzelnen Bitsprintf("Es ist jetzt %02d:%02d:%02d Uhr\n",((myTime.hex>> 0)&0b011111), ((myTime.hex>> 5)&0b111111), ((myTime.hex>>11)&0b111111));

Öffnen im Compiler Explorer

  • Aufteilung von FloatingPoint Zahlen in seine Komponenten
union float_mes { float flo; struct ieee { //aus ieee754.h kopiert#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ unsigned int negative:1; unsigned int exponent:8; unsigned int mantissa:23;#endif /* Big endian. */#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ unsigned int mantissa:23; unsigned int exponent:8; unsigned int negative:1;#endif /* Little endian. */ } ieee; int hex;};//aus ieee754.h kopiert#define IEEE754_FLOAT_BIAS0x7f /* Added to exponent. */union float_mes test={0.1};printf("float: %f\n",test.flo);printf("hex: %x\n",test.hex);printf("bin: ");for(unsigned int flag=0x80000000; flag; flag=flag>>1) printf("%c%s",test.hex&flag?'1':'0', flag&0x80800000?":":(flag&1?"\n":""));printf("bitfield: (%c)%x * 2^(%d-%d)\n",(test.ieee.negative==1?'-':'+'), test.ieee.mantissa, test.ieee.exponent, IEEE754_FLOAT_BIAS);

Öffnen im Compiler Explorer

C++

[Bearbeiten]

Bitfelder existieren mit den bekannten Ergänzungen auch in C++.

Zurück zu Grundlagen | Hoch zu Inhaltsverzeichnis | Vor zu Datatype-/Storage Class Specifier

Programmieren in C/C++: Datentypen – Wikibooks, Sammlung freier Lehr-, Sach- und Fachbücher (2024)
Top Articles
Latest Posts
Article information

Author: Lidia Grady

Last Updated:

Views: 5969

Rating: 4.4 / 5 (45 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Lidia Grady

Birthday: 1992-01-22

Address: Suite 493 356 Dale Fall, New Wanda, RI 52485

Phone: +29914464387516

Job: Customer Engineer

Hobby: Cryptography, Writing, Dowsing, Stand-up comedy, Calligraphy, Web surfing, Ghost hunting

Introduction: My name is Lidia Grady, I am a thankful, fine, glamorous, lucky, lively, pleasant, shiny person who loves writing and wants to share my knowledge and understanding with you.