Vorsicht mit unsigned Typen in C++

Translations: en

In C++ gibt es zu den ganzahligen Typen short int, int, long int und so weiter jeweils vorzeichenlose Typen, die nur nicht-negative Werte speichern können. Die vorzeichenlosen Typen erhalten das Prädikat unsigned (unsigned int usw.), die vorzeichenbehafteten Typen das Prädikat signed (signed int), was aber auch weggelassen werden kann. Eine Ausnahme bildet der Typ char, der primär für Zeichen vorgesehen ist. Bei der ebenfalls möglichen Verwendung von char als ganzzahliger Typ ist es implementationsabhängig, ob der Typ mit oder ohne Vorzeichen arbeitet. signed oder unsigned muss hier unbedingt mit angegeben werden, wenn das Programm mit allen Compilern und Systemen funktionieren soll.

Die vorzeichenlosen Typen haben den Vorteil, dass die Beträge der Zahlen größer werden dürfen, da das Vorzeichen-Bit nicht benötigt wird [1]. Da die Standards für C und C++ die Festlegung der genauen Zahlenbereiche für diese Typen der Implementierung überlassen, kann hier nur ein typischer Zahlenbereich genannt werden:

Typen mit und ohne Vorzeichen
type min max
signed int -2,147,483,648 2.147.483.647
unsigned int 0 4.294.967.295

Typischerweise kann man etwa von einer Verdopplung der Beträge bei den vorzeichenlosen Typen ausgehen.

Für die Typen gelten in C[++] folgende Regeln:

Da in C und C++ viele Typumwandlungen implizit vorgenommen werden, kann die Anwendung manchmal schwierig sein: eine unerwartete Umwandlung einer negativen Zahl in eine vorzeichenlose Zahl führt zu einem falschen Ergebnis:

unsigned int base = 10;
int a = -17;
cout << ( a % base ) << endl; // ausgabe: 9

Die Ausgabe ist hier nicht -7, wie man hier erwarten könnte. Der Grund ist, dass die int-Zahl -17 in eine unsigned int-Zahl umgewandelt wird (4294967279) und dann modulo 10 berechnet wird. Diese Umwandlung ist typisch: Ist ein Operand einer binären Operation vorzeichenlos, so wird auch der andere Operand in einen vorzeichenlosen Typen gewandelt.

Da dies nicht immer leicht zu überblicken ist, ist die generelle Empfehlung, gemischte Operationen von vorzeichenlosen und vorzeichenbehafteten Typen zu vermeiden. Dies kann durch eine explizite Konvertierung erreicht werden:

unsigned int base = 10;
int a = -17;
cout << ( a % (int)base ) << endl;  // ausgabe: -7

Eine andere Gefahr bei der Nutzung von unsigned Typen besteht darin, dass die Grenze des Wertebereichs 0 oft erreicht wird. Nun programmiert man Schleifen oft derart, dass die Schleife solange durchlaufen wird, bis die Laufvariable einen gewünschten Bereich verlässt:

for (unsigned int a = 0; a < 100; a++)  // OK.
        cout << a << endl;
for (unsigned int a = 99; a >= 0; a--)  // Endlosschleife
        cout << a << endl;

Während die erste Schleife problemlos funktioniert, bricht die zweite Schleife nicht ab. Das Problem: eine unsigned int-Variable wird nie kleiner Null, der Test a >= 0 ist immer wahr [2]. Hat a den Wert 0 und wird erneut dekrementiert, nimmt a den größten (positiven) Wert an - C und C++ testen nicht auf Überläufe oder Bereichsüberschreitungen. Das Problem ist gar nicht so leicht zu lösen. Eine funktionierende Schleife mit fallenden Werten von a hat eine verblüffende Form:

for (unsigned int a = 99; a < 100; a--)
        cout << a << endl;

[1]Die etwas vereinfachte Darstellung sei mir hier verziehen. Das Vorzeichen wird üblicherweise nicht separat gespeichert.
[2]Ein guter Compiler gibt hier eine Warnung aus.