Zkouškové období již začalo, ale (zatím) toho není tolik, abych nemohl přinést další zajímavou úlohu :). Tentokrát si vyzkoušíme tvorbu maker.
Zadání je poměrně jednoduché: napište makro SWAP, které prohodí hodnoty dvou proměnných (l-hodnot). Uvažujte pouze prohození proměnných typu char, short, int, long, float, double a ukazatelů na tyto typy.
Pokud by se vám toto nepodařilo, tak můžete zkusit implementovat zjednodušenou verzi tohoto makra (pojmenované SWAP_INT), které prohodí hodnoty dvou předaných proměnných typu int a bude fungovat korektně za všech situací.
Málokdy se stává, že existuje pouze jedno správné řešení, takže pokud už někdo s nějakým řešením přišel před vámi, můžete zkusit vymyslet řešení nové (a třeba i lepší).
Makro musí být implementačně nezávislé (tj nepoužívejte konstrukty s nespecifikovaným či nedefinovaným chováním). Dále musí fungovat při použití jak v programech podle standardu C99, tak C++98. Nesmíte definovat žádné vlastní funkce.
Nejdříve se podíváme na tu jednodušší variantu.
První, co by vás mohlo napadnout, tak je využít známý XOR trik:#define SWAP_INT(x, y) ((x) ^= (y) ^= (x) ^= (y))
Problém tohoto "řešení" je ten, že výsledek jeho vyhodnocení není definován - viz standard: Between the previous and next sequence point a scalar object shall have its stored value modified at most once...; otherwise the behavior is undefined.". Dochází zde totiž dvakrát k modifikaci proměnné x a tyto modifikace nejsou odděleny žádným sekvenčním bodem.
Zkusme tedy toto "řešení" rozdělit na tři části:
#define SWAP_INT(x, y) { \
(x) ^= (y); \
(y) ^= (x); \
(x) ^= (y); \
}
Nyní už je výsledek této operace definován, ale při testování narazíme na další z problémů - pokud zkusíme prohodit proměnnou samu se sebou, čili SWAP_INT(a, a), tak výsledkem bude 0 uložená v hodnotě a nezávisle na přechozí hodnotě této proměnné.
Řešení tohoto problému je nasnadě:
#define SWAP_INT(x, y) { \
if (x != y) { \
(x) ^= (y); \
(y) ^= (x); \
(x) ^= (y); \
} \
}
Paráda, problém vyřešen. Je zde ale ještě jeden zádrhel, který není hned vidět. Ukáže se, až se pokusíte makro použít např. takto:
if (...) SWAP_INT(a, b); else // ...
Po preprocessingu totiž dostaneme následující kód, který není syntaktický správný (nadbytečný středník před else).
if (...) { if (x != y) { (x) ^= (y); (y) ^= (x); (x) ^= (y); } }; else // ...
Jedno z možných řešení je následující:
#define SWAP_INT(x, y) do { \
if (x != y) { \
(x) ^= (y); \
(y) ^= (x); \
(x) ^= (y); \
} \
} while (0)
Nyní už bude fungovat i poslední konstrukce, takže toto řešení bych považoval za finální. Pokud by vás napadla situace, kdy selže, určitě napište komentář. Jediný problém, který mě ještě napadl, je vícenásobné vyhodnocení parametru makra, ale s tím (asi) už nic neudělám.
Mimochodem, možná vás napadlo některé z těchto řešení, které ale nefungují za každé situace:
Zatímco u té jednodušší varianty jsem v zadání vyžadoval řešení, která bude fungovat korektně za všech situací, tak u generického makra jsem to nevyžadoval, protože se mě samotnému (při zadávání úkolu) nepodařilo přijít na řešení, které by bylo funkční vždy. Mrkneme tedy na to, co se mě podařilo vytvořit a ke každému řešení napíšu pro a proti.
Protože se požadovalo, aby makro fungovalo i s jinými datovými typy než je int, tak řešení založené na aritmetických či logických operací by nefungovalo, takže bylo třeba najít jinou cestu.
Obě řešení sdílejí následující nevýhody...
...a následující výhody:
#define SWAP(x, y, type) do { \
type _tmp = (x); \
(x) = (y); \
(y) = _tmp; \
} while (0)
Specifické nevýhody:
Specifické výhody:
#define SWAP(x, y) do { \
char _tmp[sizeof(x)]; \
memcpy(_tmp, &(x), sizeof(x)); \
memcpy(&(x), &(y), sizeof(x)); \
memcpy(&(y), _tmp, sizeof(x)); \
} while (0)
Specifické nevýhody:
Specifické výhody:
Pokud chcete prohodit hodnoty dvou proměnných, nejlepší řešení (které bude funkční za všech okolností) bude si buď na to napsat funkci (v případě C++ šablonu funkce), nebo si přímo na místě prohození vytvořit dočasnou proměnnou či v C++ použít šablonu std::swap<>.
Komentáře
Zveřejněno moje řešení #3
Zveřejnil jsem moje komentované řešení třetího úkolu.
Přidat komentář