3.4 Datentypen für Ganzzahlen
Für die Speicherung von vorzeichenbehafteten Ganzzahlen (hier zunächst: standard signed integer types) bietet C die in Tabelle 3.1 aufgelisteten Datentypen. Zusätzlich finden Sie in der folgenden Tabelle die Mindestgrößen von Werten und das Formatzeichen, um den Typ mit den Funktionen der printf()-Familien formatiert auszugeben oder mit Funktionen der scanf()-Familie einzulesen. Die tatsächlichen Wertebereiche sind besonders beim Typ int aber meistens größer.
Wertebereich (mindestens) |
Formatzeichen |
|
---|---|---|
–128 |
%hhd (für Dezimal) |
|
–32.768 |
%hd oder %hi |
|
–32.768 |
%d oder %i |
|
–2.147.483.648 |
%ld oder %li |
|
–9.223.372.036.854.775.808 |
%lld oder %lli |
Tabelle 3.1 Grundlegende Datentypen für Ganzzahlen
Neben diesen vorzeichenbehafteten fünf signed-Ganzzahltypen (engl. standard signed integer types) kann es abhängig von der Implementation auch erweiterte signed-Ganzzahltypen (engl. extended signed integer types) wie etwa __int128 geben. Der neue C23-Standard enthält allerdings nur __int128 und __int256 als neuen primitiven Datentyp, und das auch nur optional. Die Arbeit mit beliebig langen Zahlen muss deshalb auch separat behandelt werden (siehe Bonus-Kapitel »Mit beliebig langen Zahlen arbeiten«, www.rheinwerk-verlag.de/5984).
Der bevorzugte Datentyp für Ganzzahlen lautet gewöhnlich int, weil per Definition die meisten Systeme mit ihm am natürlichsten umgehen und häufig auch am schnellsten rechnen können. Benötigen Sie mehr Bits (bis zu 128), steht Ihnen long oder long long zur Verfügung. Bei kleineren Werten können Sie hingegen short verwenden. Eigentlich heißen die korrekten Typnamen short int, int, long int und long long int. In der Praxis werden normalerweise nur short, long und long long verwendet (aber nicht immer).
[»] Überlauf vorzeichenbehafteter signed-Ganzzahlwerte
Bei signed-Ganzzahltypen kann es zu einem Werteüberlauf kommen. Dies bedeutet, dass z. B. bei einer Multiplikation mehr Bits entstehen, als die verwendete Variable noch aufnehmen kann. Der Standard schreibt nicht vor, wie das System auf einen Überlauf von signed-Ganzzahltypen reagieren soll. Deshalb lässt sich nicht voraussagen, wie sich das Programm weiter verhält (engl. undefined behavior). Wenn Sie beispielsweise zum maximalen Wert einer positiven Ganzzahl einen positiven Wert hinzuaddieren, befinden Sie sich unter Umständen plötzlich im negativen Bereich und haben einen Überlauf (engl. overflow) erzeugt. Ein Beispiel:
int iVal = INT_MAX; // INT_MAX enthält max. Wert für int
iVal += 2; // Überlauf (undefined behavior)
Beim Programmieren sind Sie selbst dafür verantwortlich, dass es nicht so weit kommt. Beziehungsweise müssen Sie diesen Zustand separat in Ihrem Programm feststellen. Leider können Sie jedoch in Standard-C nicht auf die Prozessor-Flags zugreifen, die z. B. einen Overflow direkt anzeigen (sogenannte Overflow-Flags). Am besten benutzen Sie in C möglichst Datentypen von der doppelten Bitbreite, die Sie eigentlich benötigen.
Die Größe der Typen int, short und long ist nicht festgelegt. int ist mindestens so groß wie short. Der Datentyp long hingegen hat mindestens die Ausdehnung eines int. Daraus können Sie auch folgern, dass nicht hundertprozentig gesagt werden kann, wie viele Bits ein bestimmter Typ verwendet. Allerdings können Sie sich mit Sicherheit auf folgende Reihenfolge verlassen:
signed char <= short <= int <= long <= long long
<= bedeutet hier: ist kleiner oder gleich.
Ähnlich sieht dies beim Abspeichern von einzelnen Zeichen aus. Auch hier hängt die Anzahl der Bits pro Zeichen von Ihrem System ab. Der Standard schreibt hier nur vor, dass signed char der kleinste und long long der größte Typ für Ganzzahlen ist. Die anderen eingebauten Typen short, int und long liegen irgendwo dazwischen. Auf vielen modernen Betriebssystemen ist heute ein char 16 Bit und ein long long 64 Bit breit. Dies liegt daran, dass für Zeichen mittlerweile nicht mehr der ASCII-Code, sondern der internationale Unicode verwendet wird, der einem Zeichen statt einem Byte nun zwei Bytes zuordnet.
Das folgende Beispiel zeigt die einfache Ausgabe von vorzeichenbehafteten Ganzzahltypen mit printf():
00 // Kapitel3/printf_beispiel.c
01 #include <stdio.h>
02 int main(void) {
03 signed char cVal = 100;
04 short sVal = 10000;
05 int iVal = 123456;
06 long lVal = 123456;
07 long long llVal = 123456;
08 printf("%hhd\n", cVal);
09 printf("%hd\n", sVal);
10 printf("%d\n", iVal);
11 printf("%ld\n", lVal);
12 printf("%lld\n", llVal);
13 return 0;
14 }
3.4.1 Vorzeichenlos und vorzeichenbehaftet
Für jeden der eben vorgestellten signed-Ganzzahltypen steht ein entsprechender unsigned-Ganzzahltyp (engl. standard unsigned integer type) zur Verfügung, der die Zahlen vorzeichenlos betrachtet. Wenn Sie beispielsweise eine Integer-Variable vereinbaren, ist diese (wenn auch implementationsabhängig) meistens vorzeichenbehaftet. Nehmen wir also an, Sie vereinbaren die folgende Variable:
int var;
Hier beträgt der Wertebereich von int abhängig von der Implementierung (siehe INT_MIN und INT_MAX) beispielsweise –2.147.483.648 bis +2.147.483.647. Mit dem Schlüsselwort unsigned können Sie jetzt eine ganzzahlige Variable ohne Vorzeichen vereinbaren. Dies sähe dann beispielsweise so aus:
unsigned int var;
In diesem Fall können Sie jedoch keine negativen Werte mehr speichern. Dafür wird der positive Wertebereich von int (abhängig von der Implementierung von UINT_MAX in der Header-Datei limits.h) natürlich größer.
[»] Größer ist nicht gleich größer
Damit Sie das jetzt nicht falsch verstehen: Der Datentyp int bleibt natürlich weiterhin gleich breit. Mit unsigned verschiebt sich lediglich der Wertebereich (abhängig von der Implementierung) von beispielsweise –2.147.483.647 bis +2.147.483.647 auf mindestens 0 bis 4.294.967.295 (siehe Abbildung 3.1). Man kann auch sagen, dass es in diesem Fall kein Vorzeichenbit (dies ist oft das oberste Bit) mehr gibt und dass sich dadurch der Wertebereich verdoppelt.
Denken Sie aber daran, dass z. B. die Zahl -1 nicht einfach durch den Hex-Wert 0x80000001 dargestellt wird (+1 mit gesetztem Vorzeichenbit), wie ein Leser einmal anmerkte, da sein printf() stets falsche Werte ausgab. Die Zahl -1 entsteht dadurch, dass von dem Hex-Wert 0x00000000 der Wert 1 abgezogen wird, und dies ergibt dann für -1 den Hex-Wert 0xffffffff, bei dem dann auch das Vorzeichenbit gesetzt ist. Im weiteren Verlauf werden Sie noch viel mehr über die Hexadezimal-Schreibweise und die verschiedenen Zahlenformate erfahren.
Gleiches wie für int gilt auch für die Datentypen short, long und long long. Bei dem Datentyp char ist es etwas komplizierter. char kann entweder signed char oder unsigned char sein. Der Datentyp char wird gesondert in Abschnitt 3.5.1 behandelt.
Mit signed gibt es auch ein Schlüsselwort, um eine Variable explizit als vorzeichenbehaftet zu vereinbaren. Allerdings entspricht die Schreibweise von
der von
int var;
Somit ist die Verwendung des Schlüsselwortes signed überflüssig (außer bei char), weil ganzzahlige Datentypen ohne Verwendung von unsigned immer vorzeichenbehaftet sind. Sie können aber ein explizites signed dazu benutzen, Ihren Quellcode besser verständlich zu machen.
Abbildung 3.1 Mit »unsigned« ändert sich nicht die Bitbreite eines Datentyps, sondern es wird lediglich der Wertebereich verschoben.
Der Vollständigkeit halber finden Sie in Tabelle 3.2 einen Überblick über die unsigned-Gegenstücke bei den Ganzzahlen. Zusätzlich finden Sie den Datentyp _Bool, der ebenfalls zu den unsigned-Ganzzahltypen gehört. Die Wertebereiche in der Tabelle entsprechen auch hier wieder dem Mindestwert. Der tatsächliche Wert hängt von der Implementierung ab.
Datentyp |
Wertebereich (mindestens) |
Formatzeichen |
---|---|---|
_Bool |
0 und 1 |
%u |
unsigned char |
0 bis 255 |
%hhu (für Dezimal) |
unsigned short |
0 bis 65.535 |
%hu |
0 bis 65.535 |
%u |
|
unsigned long |
0 bis 4.294.967.295 |
%lu |
unsigned long long (seit C99) |
0 bis 18.446.744.073.709.551.615 |
%llu |
Tabelle 3.2 Grunddatentypen für vorzeichenlose Ganzzahlen
[»] Überlauf bei »unsigned«-Ganzzahlwerten
Bei unsigned-Ganzzahlwerten kann es im Falle eines Überlaufs nicht zu einem undefinierten Verhalten kommen. Wenn hier der Wertebereich überschritten wird, wird mit einer sogenannten Modulo-Arithmetik weitergerechnet. Addieren Sie also zu einem maximalen unsigned-Ganzzahltyp z. B. 2 hinzu, wird mit 1 weitergerechnet:
unsigned int uVal = UINT_MAX; // UINT_MAX enthält max. Wert
uVal += 1; // = 0 (kein Überlauf)
Am Rande sei noch erwähnt, dass es auch hier – abhängig von der Implementation – erweiterte unsigned-Ganzzahltypen (engl. extended unsigned integer types) geben kann (zum Beispiel __uint128).
[»] Ganzzahltypen mit fester Breite
Seit C99 werden diese Datentypen über die plattformunabhängige Header-Datei stdint.h um Ganzzahltypen wie etwa int8_t, int16_t, int32_t, int64_t usw. mit fester Länge ergänzt.
3.4.2 Suffixe für Ganzzahlen
Wenn Sie den Compiler informieren müssen, wie er ein bestimmtes Literal interpretieren oder von welchem Typ dieses Literal sein soll, dann können Sie ein dem Typ entsprechendes Präfix oder Suffix zum Literal hinzufügen. Das Suffix u oder U deutet beispielsweise an, dass es sich um eine vorzeichenlose (unsigned) Zahl handelt. l bzw. L gibt an, dass es sich um eine long-Zahl handelt. Den Datentyp long long zeigen Sie mit ll bzw. LL an. Das können Sie auch mit unsigned kombinieren, indem Sie ein ul oder UL für unsigned long und ull oder ULL für unsigned long long verwenden. Verzichten Sie auf das Suffix, verwendet der Compiler int, sofern der Wert passt. Einige Beispiele wären:
unsigned int uVal = 1000u;
long lVal = 100000L;
unsigned long ulVal = 2222222UL;
long long llVal = 1234LL;
unsigned long long ullVal = 12341234323ULL;
unsigned int uHexVal = 0X42U;