Jak jistě víte, bezznaménková (angl. unsigned) čísla v C a C++ mohou nabývat hodnot od 0 do 2^sizeof(typ) - 1
. Jejich hodnota tedy není nikdy záporná. Co se ale stane, když provedete unární mínus na bezznaménkovém čísle? Něco, co zřejmě neočekáváte.
Mrkněte na zdrojový kód níže a odpovězte na následující otázku: co program po přeložení a spuštění vypíše?
#include <stdio.h> int main() { unsigned int u = 1; long int i = -u; printf("%ld\n", i); return 0; }
Než budete pokračovat ve čtení dále, zkuste se nad tím zamyslet a svoji odpověď zdůvodnit.
Co to tedy vypíše?
To závisí na tom, na jaké architektuře se program přeloží. Pokud se přeloží na systému, kde sizeof(int) == sizeof(long int)
(typicky u 32b systémů), tak vypíše -1
. Pokud bude platit sizeof(int) < sizeof(long int)
(typicky u 64b systémů), tak vypíše 2^sizeof(int) - 1
. Na 64b systému, kde sizeof(int) == 4
a sizeof(long int) == 8
, to vypíše 4294967295
.
Zajímavé. Jak to?
Důvodem je, že unární mínus v C a C++ nad unsigned int
provede tuto operaci, ale nezmění znaménkovost výsledku. Výsledek bude tedy opět typu unsigned int
. V případě -u
, kde u
má hodnotu 1
, bude výsledkem -1
neznaménkově, což je 2^sizeof(int) - 1
, protože -1
se nevleze do rozsahu unsigned int
a tudíž to podteče. Při 32b int
u to bude 4294967295
. Zbytek příkladu je závislý na tom, jaká je velikost sizeof(long int)
:
- 4 bajty: Číslo
4294967295
bezznaménkově na 4 bajtech se přiřadí do znaménkové proměnnéi
na 4 bajtech. Jelikož se ale4294967295
nevleze do rozsahu znaménkovéhoint
, dojde k přetečení a výsledkem bude-1
. Ve dvojkovém doplňku na 4 bajtech je totiž4294967295
rovno-1
. Proto se vypíše-1
. - 8 bajtů: Číslo
4294967295
bezznaménkově na 4 bajtech se přiřadí do znaménkové proměnnéi
na 8 bajtech, která jej díky své vyšší bitové šířce dokáže uchovat tak, jak je. Tudíž se do proměnnéi
přiřadí oněch4294967295
, a toto číslo se také vypíše.
Jaké z toho plyne ponaučení? Dávejte si pozor na případy, kdy používáte unární mínus na bezznaménková čísla. V některých případech se vám to může vymstít. Především si dejte pozor na kód stylu -sizeof(typ)
, protože operátor sizeof
vrací vždy bezznaménkové číslo.
2 poznámky
1) Nemyslím, že je u integer typů vhodné mluvit o "podtečení"; underflow se vyskytuje jen u floating-point typů, když je výsledek menší než nejmenší reprezentovatelná hodnota (plus minus).
2) Ad "4 bajty".
Odehrává se tu conversion do signed integer typu, přičemž hodnota v tomto typu není reprezentovatelná. V tomto případě je chování implementation-defined, nebo se raisuje implementation-defined signál. Nevím co ostatní compilery, ale GCC žádný signál neraisuje, hodnota se redukuje modulo 2^N, kde N je šířka typu, do kterého se konvertuje, tudíž -1. (Kdyby se tu odehrávalo signed overflow, chování by bylo nedefinované, zatímco unsigned overflow je OK, UINT_MAX + 1 je vždy 0.)
Re: 2 poznámky
Díky za poznámky!