Zajímavosti z C a C++: Unární mínus bezznaménkového čísla

Od Petr Zemek, 2014-02-09

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 intu 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 ale 4294967295 nevleze do rozsahu znaménkového int, 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ěch 4294967295, 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.

Obsah tohoto pole je soukromý a nebude veřejně zobrazen.

Filtrované HTML (využíváno)

  • Povolené HTML značky: <a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <table>
  • Zvýraznění syntaxe kódu lze povolit přes následující značky: <code>, <blockcode>, <bash>, <c>, <cpp>, <haskell>, <html>, <java>, <javascript>, <latex>, <perl>, <php>, <python>, <ruby>, <rust>, <sql>, <text>, <vim>, <xml>, <yaml>.
  • Řádky a odstavce se zalomí automaticky.
  • Webové a e-mailové adresy jsou automaticky převedeny na odkazy.
CAPTCHA
3 + 6 =
Vyřešte tento jednoduchý matematický příklad a vložte výsledek. Např. pro 1+3 vložte 4.
Nějak se mi tady rozmohl spam, takže poprosím o ověření.

Marek Polacek (neověřeno)

10 years 6 months zpět

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.)