Vorzeichenlose Typen in C++ (Vorzeichenlose Typen in C++), Lektion, page 722674
https://www.purl.org/stefan_ram/pub/vorzeichenlose_c++ (permalink) is the canonical URI of this page.
Stefan Ram
C++-Kurs

Vorzeichenlose Typen in C++ 

Wenn »u« eine mit »unsigned int u = 1;« deklarierte Variable ist, dann muß man sehr vorsichtig mit ihr umgehen, da nicht mehr alles zutrifft, was man von int-Variablen gewohnt ist. Beispielsweise sind »-u < 0« und »-u < u« beide »0« und »for( u = max; u >= 0; --u );« ist eine Endlosschleife! (Eine Begründung folgt weiter unten.)

Wertebereich

Ein unsigned-int-Wert

»UINT_MAX« ist mindestens 65535.

Ein unsigned-int-Objekt belegt genauso viel Speicher wie ein int-Objekt.

Da ein unsigned-int-Wert keinen Platz für ein Vorzeichen beziehungsweise für negative Werte benötigt, kann man mit demselben Speicherplatzbedarf wie bei einem int-Wert noch größere Werte darstellen.

main.cpp

#include <climits>
#include <iostream>
#include <ostream>

using namespace ::std::literals;

int main()
{ ::std::cout << UINT_MAX << '\n'; }

transcript
4294967295

Numeralia

Ein ganzzahliges Numerale, das mit einem »u« oder »U« endet und dessen Wert nicht größer als »UINT_MAX« ist, hat den Typ »unsigned int«.

main.cpp

#include <iostream>
#include <ostream>
#include <string>

using namespace ::std::literals;

int main()
{ ::std::cout << 0u << "\n"s;
::std::cout << 1u << "\n"s;
::std::cout << 2u << "\n"s; }

transcript
0
1
2

Syntax

»unsigned« ist ein Schlüsselwort in C++ , das als ein simple-type-specifier  verwendet werden kann.

Als Datentypspezifizierer ist »unsigned« synonym zu »unsigned int« und »int unsigned«, alle drei stehen für den Typ »unsigned int«.

main.cpp

#include <iostream>
#include <ostream>
#include <string>

using namespace ::std::literals;

int main()
{ { unsigned u = 0u; ::std::cout << u << "\n"s; }
{ unsigned int u = 1u; ::std::cout << u << "\n"s; }
{ int unsigned u = 2u; ::std::cout << u << "\n"s; }
{ auto u = 3u; ::std::cout << u << "\n"s; }}

transcript
0
1
2
3

Arithmetik

Bei einem vorzeichenlosen Typ folgt »0« auf den maximalen Wert. (Es handelt sich um Arithmetik in einem Restklassenring mit 2  Werten, wobei n  die Anzahl der zur Darstellung verwendeten Bits ist.)

Paare kongruenter (gleicher) Werte
           3  UINT_MAX + 4 
2 UINT_MAX + 3
1 UINT_MAX + 2
^ 0 UINT_MAX + 1
| +1 -1 UINT_MAX
| -2 UINT_MAX - 1
-3 UINT_MAX - 2
-4 UINT_MAX - 3
main.cpp

#include <climits>
#include <iostream>
#include <ostream>

using namespace ::std::literals;

int main()
{ ::std::cout << UINT_MAX + 1 << "\n"s;
::std::cout << 0u - 1 << "\n"s; }

transcript
0
4294967295

Bei einem Typ mit  Vorzeichen hätte die Addition von »1« zum maximalen Wert oder die Subtraktion der Zahl »1« vom minimalen Wert hingegen undefiniertes Verhalten. (Dies liegt daran, daß es früher C -Implementationen gab, die sich in dieser Hinsicht unterschiedlich verhielten, weil sie sich so verhielten, wie das für ihren Prozessor natürlich war. Heute sind die Prozessoren in dieser Hinsicht ähnlicher geworden, aber man behält die Regelung bei, weil sie gewisse Optimierungen erlaubt.)

Erklärung der einleitenden Beispiele

»-u < 0« ist für jeden Wert einer unsigned-Variablen falsch, weil der Typ von »-u« »unsigned int« ist und damit nie negativ  sein kann.

»-u < u« ist in dem vorausgesetzten speziellen Fall »unsigned int u = 1;« falsch, da »-u« ein sehr großer positiver Wert ist.

»for( u = max; u >= 0; --u );« ist eine Endlosschleife, da die Bedingung »u >= 0« immer wahr ist.

Typwandlung nach »unsigned int«

Bei der Wandlung eines Wertes mit einem negativen Vorzeichen wird nötigenfalls »UINT_MAX + 1« addiert, um den Wert nicht-negativ zu machen. (Allgemeiner gesagt, ist das Ergebnis einer Wandlung eines ganzzahligen Typs in einen vorzeichenlosen Typ, der kleinste nicht-negative Wert, der kongruent modulo »UINT_MAX + 1« zum Ausgangswert ist.)

Durch die Addition von »UINT_MAX + 1« entspricht »-1« dem unsigned-int-Wert »UINT_MAX«, der bei einigen Implementationen »4294967295« beträgt.

main.cpp

#include <climits>
#include <iostream>
#include <ostream>

using namespace ::std::literals;

int main()
{ ::std::cout << static_cast< unsigned >( -1 )<< "\n"s; }

transcript
4294967295

Typwandlung nach »int«

Das folgende Programm zeigt: Wenn ein Programmierer ein Ergebnis einer unsigned-Funktion versehentlich in eine int-Variable schreibt, so kann diese als negativ erscheinen, obwohl das Ergebnis der Funktion nicht negativ ist.

main.cpp

#include <climits>
#include <iostream>
#include <ostream>

static inline unsigned size()
{ return UINT_MAX; }

int main()
{ int i = size();
::std::cout << i << '\n'; }

transcript
-1

Im allgemeinen ist das Ergebnis einer solchen Wandlung nach »int« der Ausgangswert, falls er als »int«-Wert darstellbar ist. Ansonsten ist das Ergebnis der Wandlung implementationsdefiniert. Es gilt also:

Eine Wandlung von »unsigned int« nach »int« ist im allgemeinen nicht portabel.

Vergleiche

Falls ein Operand eines binären Operators den Typ »int« und der andere den Typ »unsigned int« hat, wird der int-Operand in den Typ »unsigned int« gewandelt. Daher gilt »-1« in solchen Fällen als »UINT_MAX«.

main.cpp

#include <climits>
#include <ios>
#include <iostream>
#include <ostream>

using namespace ::std::literals;

int main()
{ unsigned int one = 1;
::std::cout << ::std::boolalpha;
::std::cout <<( -1 < 1 )<< "\n"s;
::std::cout <<( -1 < 1u )<< "\n"s;
::std::cout <<( -1 < one )<< "\n"s; }

transcript
true
false
false

Ein Vergleich »0u <= n« ist immer wahr, selbst wenn »n« den Datentyp »int« hat.

Das folgenden Beispielprogramm zeigt, daß »a < b - 1« einen anderen Wahrheitswert also »a + 1 < b« haben kann, selbst wenn »a« den Typ »int« hat.

main.cpp

#include <climits>
#include <iostream>
#include <ostream>

static inline unsigned size() { return 0; }

int main()
{ { unsigned u = 1;
::std::cout <<( u < size() - 1 )<< '\n';
::std::cout <<( u + 1 < size() )<< '\n'; }

{ int i = 1;
::std::cout <<( i < size() - 1 )<< '\n';
::std::cout <<( i + 1 < size() )<< '\n'; }}

transcript
1
0
1
0

Abwärtsschleifen

Eine Schleife mit einer unsigned-int-Zählvariablen »i«, die von 2 abwärts bis 0 zählt, kann nicht das Kriterium »i >= 0« verwenden, da dieses Kriterium bei einer einer unsigned-int-Zählvariablen »i« immer wahr  ist. Die folgende Schleife zeigt eine mögliche Implementierung.

main.cpp

#include <iostream>
#include <ostream>

int main()
{ for( auto i = 3u; i--; )::std::cout << i << '\n'; }

transcript
2
1
0

Man kann beim Vergleich bestimmter Formen von Aufwärts- und Abwärtsschleifen erkennen, daß der Schleifenschritt bei Aufwärtsschleifen hinter  dem Schleifeninhalt erfolgt, bei Abwärtsschleifen davor.

main.cpp

#include <iostream>
#include <ostream>

int main()
{ { unsigned i = 0; while( i != 3 ){ std::cout << i << "\n"; ++i; }}
{ unsigned i = 3; while( i != 0 ){ --i; std::cout << i << "\n"; }}}

transcript
0
1
2
2
1
0

Inklusive Schleifen

Um bis einschließlich  einem Endwert zu zählen:

main.cpp

#include <iostream>
#include <limits>
#include <ostream>

int main()
{

{ unsigned end = UINT_MAX;
unsigned begin = end - 2;

for( unsigned i = begin; true; ++i )
{ ::std::cout << i << "\n";
if( i == end )break; }}

{ unsigned end = UINT_MAX;
unsigned begin = end + 2;

for( unsigned i = begin; true; --i )
{ ::std::cout << i << "\n";
if( i == end )break; }}

}

transcript
4294967293
4294967294
4294967295
1
0
4294967295

Überladungsauflösung

Bei der Auflösung von Überladungen wird zwischen »int« und »unsigned int« unterschieden.

main.cpp

#include <iostream>
#include <ostream>

inline void f( int ) { ::std::cout << "int\n"; }
inline void f( unsigned ) { ::std::cout << "unsigned\n"; }

int main() { f( 0 ); f( 0u ); }

transcript
int
unsigned

Empfehlungen

You should not use the unsigned integer types such as uint32_t, unless there is a valid reason such as representing a bit pattern rather than a number, or you need defined overflow modulo 2^N. In particular, do not use unsigned types to say a number will never be negative. Instead, use assertions for this.” Google C++ Style Guide 2007 – 2016 (https://google.github.io/styleguide/cppguide.html)

Falls eine Variable nicht negativ „sein darf“, könnte es in bestimmten Fällen sinnvoll sein, ihr doch den Typ »int« statt »unsigned« zu geben, da es auf diese Weise möglich ist, es durch einen Vergleich zu erkennen, falls der Variable doch ein negativer Wert zugewiesen wurde.

Wegen der vielen Probleme, die unsigned-Ausdrücke verursachen können, wird manchmal dazu geraten, ganz auf ihre Verwendung zu verzichten. Jedoch kann ein Programmierer solche Ausdrücke nicht immer vermeiden, da sie teilweise von Bibliotheken vorgegeben werden.

Manchmal wird dazu geraten, sie nicht für Zahlenwerte zu verwenden, die auch in arithmetische Operationen oder Vergleichsoperationen als Operanden verwendet werden sollen, da es bei einem Vergleich mit vorzeichenbehafteten Werten schwierig zu sagen ist, welcher der beiden Operanden in den Typ des anderen gewandelt werden soll (solch eine Wandlung ist notwendig). Von manchen C -Implementationen kann man sich Warnungsmeldungen für den Fall anzeigen lassen, daß man int- und und unsigned-int-Teilausdrücke in einem Ausdruck mischt.

Manchmal sind unsigned-Ausdrücke aber auch genau richtig, etwa wenn es gewünscht wird, daß auf den maximalen Wert wieder der Wert »0« folgt.

Teilweise wird zugunsten von unsigned-Ausdrücke ins Felde geführt, daß deren Auswertung seltener undefiniertes Verhalten hat als bei int-Ausdrücken oder, daß sie es deutlich machen, daß keine negativen Werte möglich sind, oder daß sie noch größere Werte bei gleichem Speicherplatz erlauben.

Zugunsten von int-Werten wird manchmal angeführt, daß gerade ihr undefiniertes Überlaufverhalten gewisse Optimierungen erlaubt.

Da verschiedene Bibliotheksfunktionen vorzeichenlose Typen verwenden, muß ein Programmierer vorzeichenlose Typen auf jeden Fall kennen. Wenn man jedoch die Wahl hat, sollte man auf die Verwendung vorzeichenloser Typen für Zahlenwerte verzichten (und diese nur für Werte einsetzen, die grundsätzlich nicht in Größenvergleichen oder Rechenoperationen vorkommen sollen).

Übungsfragen

?   Zählschleife

Die folgende Schleife soll von 2 abwärts bis 0 zählen. Wo steckt der Fehler? Wie könnte man ihn korrigieren?

Die Schleifenvariable »u« soll nach der Korrektur des Fehlers den Typ »unsigned« behalten und in der Schleife die drei Werte 2, 1 und 0 (in dieser Reihenfolge) durchlaufen.

fehlerhafte Schleife

#include <iostream>
#include <ostream>

int main()
{ for( unsigned u = 3u - 1; u >= 0; --u )::std::cout << u << '\n'; }

Übungsaufgaben

/   Linear-kongruente Generatoren

(Diese Übungsaufgabe setzt voraus, daß Kursteilnehmer bereits Variablen mit statischer Lebensdauer oder ähnliche Sprachmittel kennen und kann sonst übersprungen werden.)

Wenn ein unsigned-int-Wert wiederholt mit einer ganzen Zahl, die größer als 1 ist, multipliziert wird, wird er irgendwann nicht mehr wesentlich größer und ändert sich auf eine nicht immer einfach vorhersagbare Weise. Verwenden Sie diesen Effekt um eine Funktion »random()« zu programmieren, die bei jedem Aufruf eine neue unsigned-int-Zahl ergibt, die man nicht immer leicht vorhersagen kann.

About this page, Impressum  |   Form for messages to the publisher regarding this page  |   "ram@zedat.fu-berlin.de" (without the quotation marks) is the email-address of Stefan Ram.   |   A link to the start page of Stefan Ram appears at the top of this page behind the text "Stefan Ram".)  |   Copyright 1998-2014 Stefan Ram, Berlin. All rights reserved. This page is a publication by Stefan Ram. relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722674 stefan_ram:722674 Vorzeichenlose Typen in C++ Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722674, slrprddef722674, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Copyright 1998-2014 Stefan Ram, Berlin. All rights reserved. This page is a publication by Stefan Ram.
https://www.purl.org/stefan_ram/pub/vorzeichenlose_c++