Možná jste se již setkali s různými zápisy použití operátoru sizeof
. Konkrétně se jedná např. o zápisy sizeof(int)
, sizeof(*p)
a sizeof *p
. Jaký je mezi těmito zápisy rozdíl a kdy je nutné použít závorky se dočtete v následujícím příspěvku.
Co je to ono sizeof
?
Ve světě jazyků C a C++ je sizeof
operátor, který slouží pro zjištění velikosti typu či výrazu (viz sekce 6.5.3 v ISO C11 standardu a sekce 5.3.3 v ISO C++11 standardu). Navrácená hodnota je v bajtech. Jedno z jeho možných použití je při dynamické alokaci paměti:
int *p = malloc(10 * sizeof(int));
S tímto užitím jste se pravděpodobně již setkali. Jak jistě víte, tak funkce malloc()
vrací ukazatel na dynamicky alokovanou paměť o zadané velikosti (či nulový ukazatel při selhání). Pokud bychom na tomto místě nepoužili sizeof
, tak by náš kód nebyl přenositelný. Skutečně, následující kód předpokládá, že velikost typu int
je 32 bitů (4 bajty):
int *p = malloc(40);
Dále, pokud bychom napsali jen
int *p = malloc(10);
tak dostaneme ukazatel na paměť o 10 bajtech, nikoliv paměť pro uložení 10 čísel typu int
(to bychom potřebovali oněch 10 * sizeof(int)
bajtů).
Co je na tom zajímavého?
Na výše uvedeném použití sizeof
není zajímavého nic. Možná jste se ale již setkali s následující alokací dynamické paměti:
int *p = malloc(10 * sizeof *p);
Pokud jste se s tímto nesetkali, tak vaše reakce bude pravděpodobně něco ve stylu: "Huh?!". Pojďme si to vysvětlit. Když nahlédneme do normy jazyků C a C++ (viz reference uvedené na začátku příspěvku), tak zjistíme, že sizeof
je operátor (stejně jako třeba +
či ++
) a že jej lze použít dvěma způsoby:
sizeof (type-id) sizeof unary-expression
U našeho prvního příkladu jsme použili první způsob, tedy za sizeof
jsme uvedli v závorkách typ. Podobně bychom mohli použít něco takového:
typedef struct { int x; int y; } Point; Point *p = malloc(sizeof(Point));
Výše uvedený kód nám alokuje místo pro jeden objekt typu Point
. Pokud bychom napsali
Point *p = malloc(sizeof Point); // způsobí chybu při překladu
tak nám překladač vynadá, protože v případě typu je nutné za sizeof
uvést závorky. Když ale napíšeme
Point *p = malloc(sizeof *p);
tak je vše v pořádku. Výše zmíněný kód je pak funkčně ekvivalentní tomu původnímu kódu, kde jsme použili sizeof(Point)
. Důvod je ten, že nyní používáme druhou formu sizeof
, která nám umožňuje získat velikost typu podle předaného výrazu.
Pokud nyní přemýšlíte nad tím, jak to může fungovat, když se zdá, že přistupujeme na neinicializovaný ukazatel, tak důvodem je, že sizeof *p
nám vrátí pouze velikost operandu, aniž by jej vyhodnocovalo (viz výše zmíněné odkazy do normy). Toto použití je tedy naprosto bezpečné.
Kdy je vhodné použít onen druhý způsob?
Já osobně používám druhý způsob na místech, kde to je nutné či to zlepší udržovatelnost programu. Jako ukázku si uvedeme případ, kdy chci v Céčku iterovat přes všechny položky staticky alokovaného pole:
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for (size_t i = 0; i < sizeof numbers / sizeof numbers[0]; ++i) { // Zde něco provedeme s numbers[i]. }
Toto je udržovatelnější, než
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for (size_t i = 0; i < 10; ++i) { // ... }
či
#define SIZE 10 int numbers[SIZE] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for (size_t i = 0; i < SIZE; ++i) { // ... }
protože v prvním případě není při změně (přidání, odebrání) položek z pole nutné změnit nic dalšího. To snižuje pravděpodobnost, že někde něco zapomeneme změnit, což je z hlediska udržovatelnosti žádané.
Další ukázka, kdy se výše uvedený postup hodí, je tento:
int *p = malloc(10 * sizeof(int));
Místo toho je z hlediska udržovatelnosti vhodnější psát
int *p = malloc(10 * sizeof *p);
protože pokud bychom změnili typ p
na něco jiného, než int *
, tak nám tuto změnu stačí provést na jediném místě: v int *p
. V opačném případě se nám může stát, že zapomeneme změnit typ v operátoru sizeof
, což může mít fatální důsledky.
Co říci na závěr?
Jak jsem již zmiňoval, tak při výše uvedeném použití druhé formy operátoru sizeof
nejsou závorky nutné. Je to ze stejného důvodu, proč nejsou nutné u výrazu ++(i)
. Konstrukce ++
je operátor, stejně jako sizeof
. Tudíž, co je třeba si uvědomit, tak je, že sizeof
není funkce, nýbrž operátor. U použití
int *p = malloc(10 * sizeof(int));
jsou pak ty závorky kolem int
součástí operandu, nikoliv sizeof
, tedy možná méně matoucí by bylo to psát s mezerou:
int *p = malloc(10 * sizeof (int));
To se ale většinou nedělá. Tak či tak, já osobně tam ty závorky doporučuji psát, i když tam nejsou nutné, protože velmi málo lidí ví, kdy závorky jsou a nejsou nutné a takový kód by je mohl zbytečně zaskočit. Proto osobně píši
int *p = malloc(10 * sizeof(*p));
místo
int *p = malloc(10 * sizeof *p);
Co se týče nové normy jazyka C++ (ISO C++11), tak tam nám v souvislosti se zavedením podpory pro variadické šablony (šablony s proměnným počtem typových parametrů) přibyla ještě jedna forma sizeof
:
sizeof ... (identifier)
O té ale někdy jindy :).
Dík za fajn články o
Dík za fajn články o zaujímavých/nových vlastnostiach C/C++. Osobne používam pre zistenie veľkosti staticky alokovaného poľa nasledovnú konštrukciu, ktorá zvyšuje čitateľnosť programu a je zároveň udržiavateľná:
Re: Dík za fajn články o
Díky za tip!