C/C++ programozási trükkök/Trükkök makrókkal


Ebben a fejezetben a különböző makrókkal kapcsolatos trükkök kerülnek terítékre. A makrókat a C preprocesszor értékeli ki. A C preprocesszor részletes angol nyelvű leírással rendelkezik.[1]

Elágazások tuningolása

szerkesztés

A következőek C/C++ nyelven, a GCC fordító 2.96 vagy frissebb verziójával működnek. Más fordítóról nincs információ.

Sok esetben egy elágazásnál (if) az egyik eset lényegesen sűrűbben fordul elő, mint a másik. Például elég sűrűn vannak a programokban biztonsági ellenőrzések, mint például egy osztás előtt annak megvizsgálása, hogy az osztó nem nulla-e. A fordítóprogram a várható eredmény érdekében képes optimalizálni a kódot, ehhez viszont a programozónak jelezni kell az elágazás várható kimenetelét. A GCC 2.96-os verziójától kezdve erre a __builtin_expect[2] makrót lehet használni. Nézzünk először egy példát!

#ifndef LIKELY
#if __GNUC__ >= 3

#define LIKELY(x)  __builtin_expect((x),1)
#define UNLIKELY(x) __builtin_expect((x),0)
#else 

#define LIKELY(x) (x)
#define UNLIKELY(x) (x)
#endif
#endif

...

if (LIKELY(a>b)) {a=b;}
          else   {a=0;}

A fenti példa először is definiál két új makrót a LIKELY-t és az UNLIKELY-t. Ezek nem csinálnak semmit, ha 3.00-ásnál korábbi GCC-vel fordítjuk a kódot (vagy esetleg más fordítóval), de bekapcsolja a __builtin_expect-et, ha ismeri ezt a fordító. Térjünk vissza a példa elemzésére! Ha a feltétel valószínű, akkor a LIKELY arra ösztönzi a fordítót, hogy az else ág legyen kevéssé hatékonyan megvalósítva, konkrétan egy ugró utasításon keresztül, míg az igaz ág egyenesen, ugrás nélkül lesz gépi kódra fordítva. A modern processzorok elkezdik adott utasítás végrehajtása közben már feldolgozni a következő utasításokat, de a feltételes ugró utasításoknál ez nehezen megy (kétfelé kellene folytatnia az előfeldolgozást). Ehhez jön még az a trükk, hogy a processzorok az utasításcache-be beemelik a következő utasításokat. E két dolog következménye az, hogy az ugrás nélkül megvalósított ága az elágazásnak gyorsabban hajtódik végre, mint a másik ág.

Kommentezés makrókkal[3]

szerkesztés

A következőek C/C++ nyelven bármilyen fordítóval működnek.

A C megjegyzéseket nem lehet rendesen egymásba ágyazni, a makrókat viszont igen, ahogy azt a következő kód szemlélteti.

#if 0

printf("Kommentezett részlet.");
#if 0
printf("Először kommentezett részlet.");
#endif 

#endif

Mivel az #if fordítási időben értékelődik ki és a 0 miatt mindig hamis lesz, ezért az if/endif páros közötti kód nem kerül lefordításra. Ennek a megközelítésnek akkor van értelme, ha szeretnék 10-20 függvényt, vagy egy nagyobb osztályt átmenetileg eltávolítani a fordításból, de azok sok megjegyzéssel vannak ellátva, ezért nem könnyű egy darab nagy /* */ megjegyzésblokkba rakni őket.


Debugolás makrókkal[4]

szerkesztés

A következőek C/C++ nyelven, bármilyen fordító alatt működnek.

Mottó: Nincs tökéletes program, csak olyan, aminek nem ismejük a hibáit. Egy programot célszerű jó alaposan ellátni nyomkövetéshez használatos információkkal, így hamar kiderülnek a hibák. Egy kész, kereskedelmi programnál viszont egyrészt ezek az információk lassítják a programot, csúnyává teszik a kimenetet, olyan belső információkat tartalmaznak, ami nem a felhasználóra tartozik, vagy csak valamilyen okból nem szeretnénk bent hagyni a kódban. Nézzünk példát!

#ifdef DEBUG_ON
#include <iostream>
#define DEBUG(expr) expr
#define MESSAGE(msg) std::cout<< msg 
#define MESSAGELN(msg) std::cout<< msg << std::endl
#else
#define DEBUG(expr) 
#define MESSAGE(msg) 
#define MESSAGELN(msg) 
#endif
...
DEBUG(if (a<b) abort();)

A most definiált makróknak nincs hatásuk, ha nem definiált a DEBUG_ON. Ha definiált, akkor a következőt csinálják: A MESSAGE kiír egy üzenetet a képernyőre. Ez lehet sima karakterlánc is, de bármely összetettebb objektum is C++-ban, amelynek meg van írva az alkalmas << operátora. A MESSAGELN ugyanezt csinálja, csak új sor karakterrel végződik. A DEBUG egy kódrészletet fog körbe, ami csak DEBUG módban fut le. Demonstrációként nézzük meg hogyan lehet a makrók definíciója során egyszerűbbé tenni a DEBUG-gal a kódot. Az alábbi kód pontosan ugyanazt csinálja, mint az előző, csak másként van írva:

#ifdef DEBUG_ON
#include <iostream>
#define DEBUG(expr) expr
#else
#define DEBUG(expr) 
#endif

#define MESSAGE(msg) DEBUG(std::cout<< msg)
#define MESSAGELN(msg) DEBUG(std::cout<< msg << std::endl)
...
DEBUG(if (a<b) abort();)

Biztonságos memóriakezelés[5]

szerkesztés

A következőek C/C++ nyelven, bármilyen fordító alatt működnek.

Sokszor kell memóriát lefoglani, s bizony a programozó időnként hibásan szabadítja fel. Vannak ennek a detektálására automatikus programok, például a w:Cppcheck, de a programozó is törekedhet minél jobb program írására.

Az alábbi példaprogram a következőt ötleten alapul: egy null pointert felszabadítani nem lehet, az hibát okoz. Azért lehet null pointer a változóban, mert vagy elfelejtettünk memóriát foglalni, vagy már egyszer felszabadítottuk. Egyik sem vészesen nagy probléma, tehát egyszerűen ilyenkor nem kell tennünk semmit, vagy legfeljebb üzenetet küldünk a programozónak, hogy majd próbálja megkeresni ennek az okát. Túlzott óvatosságnak tűnik, de a free() implementációjában semmi sem garantálja[6], hogy a pointert kinullázza felszabadítás után, ezért a biztonság kedvéért explicit beállítjuk felszabadítás után. Nézzük a példát:

#define free(pointer) if (LIKELY(pointer!=0)) {free(pointer);pointer=0;} DEBUG(else {MESSAGE("Free(?!) a null:");MESSAGELN(#ptr););}
#endif
...
free(ptr);

A példában látszik, hogy felhasználtunk pár korábbi makróit is. Nincs rájuk szükség, de egy komplex példán keresztül jobban meg lehet érteni a makrók működését.

Vannak, akik szerint az ilyen rejtett makrók veszélyesek. Ez esetben érdemes a makrót csupa nagybetűs névvel használni (FREE), s így egyértelműen látszik az átdefiniálás.

Hivatkozások

szerkesztés