Logo GNU
Kodovani P°edchozφ Nßsledujφcφ Obsah

9. Roz╣φ°enφ jazyka GNU C

Jazyk GNU C mß n∞kterΘ u╛iteΦnΘ ro╣φ°enφ oproti Ansi C, kterΘ se hodφ hlavn∞, kdy╛ chcete mφt program hodn∞ rychl², a prßv∞ o nich se tu m∙╛ete n∞co doΦφst. O roz╣φ°enφch G++ oproti C++ se tu zmi≥ovat nebudu.

9.1 Inline funkce versus built-in

Jazyk C se kompiluje jen do malΘ podmno╛iny instrukcφ poΦφtaΦe. Standard °φkß, ╛e ostatnφ (jako t°eba in, out, cli apod.) jsou p°φli╣ zßvislΘ na architektu°e, ne╛ aby se dßvaly do jazyka a jedinß sprßvnß cesta je volat je z knihovnφch funkcφ, kterΘ jsou psanΘ v assembleru.

Bohu╛el Φasto se tyto funkce pou╛φvajφ ke zrychlenφ v²sledku. Existuje nap°φklad mnoho chytr²ch cest, jak kopφrovat pam∞╗ (rep movsb na xt a 286, po 32bitov²ch blocφch na 386 a 486, p°es koprocesor na pentiu..), kterΘ ani ten nejchyt°ej╣φ p°ekladaΦ z jednoduchΘho for cyklu nevymyslφ. Nebo jsou situace, kdy takovΘ instrukce musφte pou╛φvat Φasto (t°eba outin v hardwarov²ch driverech) a jejich volßnφ jako funkcφ zdr╛uje, zv∞t╣uje k≤d a p°inß╣φ dal╣φ potφ╛e.

Jedna odpov∞∩ na to je built-in. Do p°ekladaΦe se zaintegrujφ nejΦast∞j╣φ takovΘ p°φpady (jako je memset, outb apod.) a ten potom umφ sßm generovat optφmßlnφ k≤d. Takov²ch funkci je hodn∞ (podφvejte se do string.h a math.h nap°φklad sin, cos, abs, fabs, strcpy, strdup, memcpy, memmove a takhle bych mohl pokraΦovat je╣t∞ hodn∞ dlouho) °ßdov∞ se to pohybuje podle mΘho nßzoru ve stovkßch. JednotlivΘ p°ekladaΦe se Φasto triumfujφ v tom, kdo mß vφce takov²ch funkcφ integrovan²ch, a n∞kterΘ - jako t°eba Watcom C - jich umφ opravdu hodn∞.

Na druhou stranu je to jenom jakßsi o╣klivß v²pomoc, kterß funguje jen v nejΦast∞j╣φch p°ipadech. P°edstavte si t°eba, ╛e jste vymysleli bezvadnou cestu jak implementovat nßsobenφ ve fixedpointu a chcete to pou╛φt ve sv²ch programech. V knihovn∞ takovß funkce ale nenφ a tak ani compiler ji nemß jako builtin. A mßte sm∙lu. Takov²ch p°φpad∙ jsou doslova tisφce - nap°φklad memset pro 16ti a 32 bitovß Φφsla, kter² se hodφ pro truecolor apod. Navφc se to rozchßzφ s filozofiφ C jako minimßlnφho ale maximßln∞ rychlΘho jazyka.

V GCC v²vojß°i zvolili jinou cestu. Builtin je opravdu jenom n∞kolik zßkladnφch funkcφ (abort, abs, alloca, cos, exit, fabs, ffs, labs, memcmp, memcpy, sin, sqrt, strcmp, strcpy, strlen) a to hlavn∞ proto, ╛e jsou natolik ΦastΘ, ╛e je t°eba nejenom generovat optimßlnφ k≤d bez volßnφ funkce ale t°eba i p°edpoΦφtßvat jejich v²sledek pro konstantu a d∞lat dal╣φ podobnΘ optimalizace specifickΘ pro danou funkci. Mφsto toho ale do GCC p°idali n∞kolik roz╣φ°enφ tak, aby b∞╛n² programßtor, ani╛ by m∞nil p°ekladaΦ, si mohl vytvo°it svoje vlatsnφ funkce, kterΘ se chovajφ podobn∞ jako built-in. Tyto roz╣φ°enφ se navφc pou╛φvajφ i v mnoha jin²ch p°φpadech. Je to:

9.2 Roz╣φ╛enφ ╣ikovnß hlavn∞ pro optimalizaci

Mezi nejzajφmav∞j╣φ roz╣φ°enφ pro optimalizaci pat°φ:

9.3 Ostatnφ

Gcc mß mnoho dal╣φch roz╣φ╛enφ jako je nap°φklad:

Toto nenφ ale kompletnφ seznam. GNU C obsahuje i dal╣φ vφce Φi mΘn∞ u╛iteΦnΘ roz╣φ°enφ. Rßd bych vφce popsal ty podle mΘho nßzoru nejzajφmav∞j╣φ.

9.4 Roz╣φ°enß syntax asm

Jako skoro ka╛d² p°ekladaΦ C i GCC mß mo╛nost vklßdßnφ inline assembleru. V GCC to je ale °e╣eno o dost odli╣n∞. V∞t╣ina lidφ se toho d∞sφ a ptß se, proΦ to u GNU neud∞lali normßln∞. Nev∞dφ ale, ╛e to bylo takto vymy╣leno pro jejich dobro. ╪e╣enφ v GCC mß toti╛ n∞kolik v²hod.

Na prvnφ pohled ka╛dΘho p°ekvapφ zm∞n∞nß syntax. Nejjednodu╣╣φ pou╛itφ ASM vypadß asi takto:

GCC: asm("cli");
BC:  asm { cli }
ProΦ to bylo takto ud∞lßno? V²hoda je jednoduchß - nemate to programy, kterΘ znajφ C, ale neznajφ GCC. Pokud vidφ zßpis z GCC, °eknou si, ╛e to je volßnφ funkce a p°edßnφ stringu, zatφmco u verze z BC se n∞kolikrßte podφvajφ na asm a pak dojdou k zßve°u, ╛e tam chybφ st°ednφk, to samΘ se opakuje u cli atd.

Dal╣φm rozdφlem je to, ╛e GCC pou╛φvß AT&T syntax assembleru. To samo osob∞ nep°inß╣φ ╛ßdnou v²hodu ale ani nev²hodu. GCC funguje tak, ╛e cel² °et∞zec v asm prost∞ po╣le dßl assembleru a tak v∙bec nic o assembleru v∞d∞t nemusφ. To mß tu v²hodu, ╛e nap°φklad m∙╛ete pou╛φt MMX instrukce, pokud je umφ assembler a nemusφte kv∙li tomu shßn∞t novou verzi GCC, kterß by pro MMX m∞la n∞jakou specißlnφ podporu.

Navφc jsou programßto°i, (jako jß) kte°φ pova╛ujφ AT&T syntax za normßlnφ a nechßpou jak mohli intelovΘ tu jejich tak zkazit.

Nejd∙le╛it∞j╣φ rozdφl ale je v optimalizaci. Pokud p°ekladaΦ uvidφ jednoduch² zßpis:

asm {
    mov ex,16
    mov cx,si
    int 17
}
u╛ si nem∙╛e b²t niΦφm jist - interrupt mohl klidn∞ zm∞nit v╣echny registry, globßlnφ prom∞nΘ a je╣t∞ p°erovnat zßsobnφk. Prost∞ jeho p°edstava o sv∞t∞ se zhroutφ a nezb²vß mu, ne╛ aby v╣echny snahy o optimalizaci vzdal.

Ale ani u jednodu╣╣φch p°φklad∙ si nem∙╛e b²t jist. Nem∙╛e znßt celou instrukΦnφ sadu (proto╛e se stßle objevujφ novΘ procesory a klony s nov²mi instrukcemi - viz MMX), v╣echny vedlej╣φ uΦφnky, chyby v procesoru apod. a tak jednodu╣╣e nem∙╛e nic po°ßdnΘho o takovΘm kusu assembleru p°edpoklßdat. A to ani o samotnΘ instrukci cli v minulΘm prφpad∞. P°edstavte si, ╛e pφ╣ete program, kter² Φasto zapφna a v²pφnß interrupty, ud∞lßte si tedy inline funkce pro cli a sti:

static inline cli(void) { asm {cli}}
static inline sti(void) { asm {sti}}
Tyto funkce volßte z nejr∙zn∞j╣φch internφch smyΦek (co╛ je celkem b∞╛nΘ u ovladaΦ∙), chudßk p°ekladaΦ musφ b²t zmaten a vyprodukovat stra╣n² k≤d.

GCC je na tom lΘpe. Pokud u asm explicitn∞ ne°eknete, ╛e n∞co m∞nφ, p°edpoklßdß se, ╛e nem∞nφ nic. To jde tak daleko, ╛e u funkcφ:

static inline cli(void) { asm("cli");}
static inline sti(void) { asm("sti");}
dojde n∞kdy dokonce k zßv∞ru, ╛e kdy╛ takovß funkce nic nem∞nφ, je nejlep╣φ ji v∙bec nevolat, zaΦne chytraΦit a volßnφ vyoptimalizuje pryΦ (nebo alespo≥ odstranφ ze smyΦy, aby se to neprovßd∞lo zbyteΦn∞ Φasto). Tomu se dß zamezit pomocφ volatile. V ansi C je definovßno, ╛e kdy╛ uvedete flag volatile u prom∞nΘ, je nutnΘ p°edpoklßdat, ╛e mß n∞jaky specißlnφ v²znam (nap°φklad je hlφdßna a m∞n∞na z ΦasovaΦe) a tak nenφ mo╛nΘ na nφ d∞lat n∞kterΘ optimalizace (jako p°edpoklßdat jakou bude mφt hodnotu, uklßdat ka╛dou chvφli jinam, vyhodit ji ·pln∞ apod.) U asm toto funguje podobn∞. Zßpis:
static inline cli(void) { asm volatile ("cli");}
static inline sti(void) { asm volatile ("sti");}
u╛ v╣echno bude fungovat tak, jak mß, a k≤d bude optimalizovßn, jako kdyby tam ╛ßdnΘ cli, nebo sti nebylo.

Ale po°ßd to nenφ ono - optimalizace jde pou╛φvat jen u n∞kter²ch hodn∞ hloup²ch funkcφ jako je cli, kterΘ nic nem∞nφ (ani registry) a nic neΦtou (proto╛e optimizer m∙╛e usoudit, ╛e se mu hodφ vß╣ assembler provΘdst jindy a funkci m∙╛e celou p°ehßzet). proto ani nem∙╛ete p°edpoklßdat, ╛e u╛ v╣echno co jste napsali p°ed asm je u╛ hotovΘ.

A proto mß asm dal╣φ ro╣φ°enφ. Za samotn²m stringem m∙╛ete napsat : a zadat, jakΘ mß funkce v²stupy, jakΘ vstupy a co modifikuje. To dß optimizeru p∞kn² obrßzek o tom, co vlastn∞ vß╣ program d∞lß a m∙╛e provßd∞t dal╣φ optimalizace.

Vstupnφ a v²stupnφ parametry jsou v assembleru potom p°φstupnΘ jako %0, %1 atd. (vstupy nap°ed, v²stupy potom.) p°i kompilaci GCC potom projde string s assemblerem na % kombinace a nahradφ je prav²m umφst∞nφm prom∞nΘ. Aby ale nedochßzelo ke kolizφm z oΦφslovan²mi registry, je nutnΘ u asm ze vstupy a v²stupy psßt dv∞ % u registr∙, tedy %%eax mφsto %eax. Nap°φklad:

asm volatile ("outb %1, %0"
    :
    : "d" (port),
      "a" (data));
°φkß, ╛e assembler mß dva parametry (port a data), a ty nem∞nφ. Proto╛e funkce mß vedlej╣φ ·Φφnek, kter² lze te╛ko definovat, je nutnΘ pou╛φt volatile. Prvnφ dvojteΦka °φkß, ╛e assembler nemß ╛ßdnΘ v²stupy, dal╣φ dvojteΦka odd∞luje vstupy (to je port a data). magickß kombinace "d"(port) se sklßdß ze dvou Φßstφ - t°φdy "d" a parametru (port) a °φkß, ╛e prom∞nß port mß b²t ulo╛ena v registru edx. Druh² parametr odd∞len² Φßrkou mß t°φdu "a" tedy registr eax. GCC podporuje mnoho t°φd pro ulo╛enφ dat. Zßkladnφ jsou: Ale existujφ i dal╣φ exotiΦt∞j╣φ t°φdy, kdo to myslφ s psanφm asm konstrukcφ vß╛n∞ by m∞l prostudovat manußl (info system). Nap°φklad 'N' znamenß konstantu 0--255, 'M' 0--3, 'O' je adresa, ke kterΘ jde p°iΦφtat offset apod. Je mo╛n∞ uvΘdst vφc t°φd narßz ("SD" znamenß, ╛e parametr mß b²t v registru edi nebo esi)

Tento formßt vychßzφ ze zp∙sobu, jak²m GCC uchovßvß RTL instrukce a machine description (popis architektury).

Funkce out fungujφcφ i pro konstantφ port je tedy:

       asm volatile ("outb %1, %0"
          :
          : "Nd" (port),
            "a" (data));
A u╣et°φte tφm jeden registr a instrukci pro nastavovßnφ eax. Te∩ to vpodstat∞ °φkß, ╛e instrukci outb jde pou╛φvat bu∩ pro konstantnφ port, nebo pro hodnotu ulo╛enou v dx a pro data ulo╛enß v ax, co╛ je p°esn∞ to, jak se out chovß. U % parametr∙ je takΘ mo╛nΘ p°etypovßvat, pokud nenφ zaruΦeno, ╛e parametry jsou toho sprßvnΘho typu. T°eba jde pou╛φt %b0 pokud to mß b²t byte. Jsou podporovßny nßsledujφcφ typy: Navφc pokud pot°ebuje k≤d nap°φklad registr ax nastaven² na 1, je mnohem lep╣φ uvΘdst ax mezi vstupy (a jako parametr napsat 1), ne╛ zaΦφnat k≤d p°φkazem movw $1,%ax, proto╛e GCC tak m∙╛e n∞jak jinak za°φdit nastavenφ ax na 1 a u╣et°it tak instrukci.

Za prvnφ dvojteΦku se pφ╣e v²stup - to je parametr, kter² musφ b²t lvalue. GCC poΦφtß s tφm, ╛e jeho hodnota se pouze zapisuje ale neΦte. U t°φdy je nutnΘ psßt '=':

asm volatile ("inb %1, %0"
    : "=a" (rv)
    : "Nd" (port));
Toto naΦte z portu port hodnotu do prom∞nΘ rv.

Pokud chcete vstupn∞ v²stupnφ prom∞nΘ, m∙╛ete pou╛φt nßsledujφcφ konstrukci:

asm ("incl %0": "=g" (i): "0" (i));
Toto provede i++ pro prom∞nou i ulo╛enou kdekoliv. Podobnou °ßdku najdete i v souboru i386.md (machine description pro 386). "0" °φkß, ╛e tento parametr musφ b²t ulo╛en na stejnΘm mφst∞ jako parametr Φφslo 0 (v²stupnφ i). Pokud to tam pou╛ijete "g", gcc nebude mφt pocit, ╛e to prvnφ i n∞jak souvisφ s tφm druh²m a bude na n∞ nahlφ╛et jako na dv∞ r∙znΘ prom∞nΘ a pro ka╛dou z nich m∙╛e t°eba zvolit jin² registr, podle toho, jak se to ve zbytku k≤du hodφ. Navφc gcc m∙╛e v²stupnφ parametry umφstit na stejnΘ mφsto jako vstupnφ, proto╛e p°edpoklßdß, ╛e vstupy se nap°ed naΦtou, pak se provede n∞jakΘ zpracovßnφ a potom se ulo╛φ do v²stup∙. Pokud vß╣ k≤d mixuje vstupy a v²stupy, je nutnΘ k v²stupnφ t°φd∞ p°idat "&" jako v nßsledujφcφm getpixelu:
asm (
      "movw %w1, %%fs
       .byte 0x64
       movb (%2, %3), %b0"
   : "=&q" (result)               /* v²stup in al, bl, cl, nebo dl */
   : "rm" (seg),                  /* segment selector v reg, nepo pam∞ti */
     "r" (pos),                   /* zaΦßtek °ßdky*/
     "r" (x)                      /* pozice na °ßdce*/
   );
Poslednφ d∙le╛itß v∞c je to, ╛e obΦas takovΘ assemblerovΘ programy pot°ebujφ registry. Jedna z cest je na zaΦßtku ulo╛it modifikovanΘ registry na stack a na konci vyzvednout. Nenφ to ale nejlep╣φ a GCC nabφzφ jinou cestu. Za poslednφ : m∙╛ete napsat seznam registr∙, kterΘ jste modifikovali a "cc" pro zm∞nu flag∙. Pokud k≤d modifikuje a Φte pam∞╗ nejak²m podivn²m zp∙sobem (jinak, ne╛ jsou jenom zm∞ny prom∞n²ch), je nutnΘ napsat i "memory". To za°φdφ, aby se v╣echny prom∞nΘ ulo╛ily do pam∞ti, ne╛ se k≤d provede a potom se p°edpoklßdalo, ╛e se mohly zm∞nit. Navφc je u asm statement∙ modifikujφcφch pam∞╗ (nap°φklad ekvivalent pro memcpy) Φasto nutnΘ pou╛φvat volatile, proto╛e pam∞╗ nenφ vedena ani mezi vstupy ani mezi v²stupy a tak optimizer nevidφ d∙vod, proΦ by takovo² k≤d nemohl p°emis╗ovat, vyhodit ze smyΦky apod.

Nap°φklad nßsledujφcφ k≤d funguje jako memcpy a kopφruje n byt∙ ze src do dest (toto je ale jen ukßzkov² p°φklad a cesta p°es rep movsb je velmi pomalß):

asm volatile (
  "cld
   rep
   movsb"
   :                               /*bez v²stupnφch prom∞n²ch*/
   :"c" (n),"S" (src),"D" (dest)   /*do cx poΦet, do si zdroj, di cφl*/
   :"cx","si","di","memory","cc"); /*modifikovanΘ registry, pam∞╗ a flagy*/
To je asi kompletnφ syntax. Mo╛nß vßm nenφ ·pln∞ jasnΘ k Φemu je takto obecnß syntax nutnß. Je to prßv∞ kv∙li inlinovßnφ funkcφ. Pokud pφ╣ete kus assembleru do svΘho k≤du, je situace mnohem jednodu╣╣φ - u╣ijete ho na mφru danΘ situaci. Kdy╛ ale d∞lßte inline funkci, je lep╣φ dßt optimizeru v∞t╣φ volnost.

Nakonec jedno velkΘ varovßnφ. nauΦte se po°ßdn∞ tuto syntax, ne╛ zaΦnete programovat. Je zdrojem Φast²ch chyb. Zapomenete na n∞jakou drobnost - t°eba uvΘdst volatile a ono to fungovat m∙╛e a nemusφ. TakΘ se m∙╛e dost dob°e stßt, ╛e to funguje ale jen 999 z tisφce pokus∙, nebo tak, ╛e to je nakonec pomalej╣φ, ne╛ kdybyste to napsali v C. NejΦast∞j╣φ chyby jsou:

Pokud chcete, aby gcc kompilovalo va╣e funkce bez °eΦφ i v -pedantic m≤du, je nutnΘ nepsat stringy na n∞kolik °ßdek a ka╛dou ukonΦit pomocφ \n" a novou zaΦφt pomocφ ". Vypadß to potom stra╣n∞, ale co se dß d∞lat. StarΘ C neum∞lo vφce°ßdkovΘ stringy. TakΘ je mo╛nΘ pou╛φvat __asm__ mφsto asm__volatile__ mφsto volatile.

Nakonec jenom n∞kolik chyb²ch a neefektivnφch funkcφ, kterΘ jsem p°i psanφ tohoto Φlßnku nßhodou objevil v r∙zn²ch zdrojßcφch. Nalezenφ nedostatk∙ ponechßm Φtenß°i jako jednoduchΘ cviΦenφ. Jak vidφte i velcφ mist°i se obΦas utnou (a obΦas jim to i projde).

extern inline void * memmove(void * dest,const void * src, size_t n)
{
register void *tmp = (void *)dest;
if (dest<src)
__asm__ __volatile__ (
        "cld\n\t"
        "rep\n\t"
        "movsb"
        : /* no output */
        :"c" (n),"S" (src),"D" (tmp)
        :"cx","si","di");
else
__asm__ __volatile__ (
        "std\n\t"
        "rep\n\t"
        "movsb\n\t"
        "cld\n\t"
        : /* no output */
        :"c" (n), "S" (n-1+(const char *)src), "D" (n-1+(char *)tmp)
        :"cx","si","di","memory");
return dest;
}
                   -- linux kernel, linux/include/asm/string-486.h
extern __inline__ void
outportb (unsigned short _port, unsigned char _data)
{
  __asm__ __volatile__ ("outb %1, %0"
          :
          : "d" (_port),
            "a" (_data));
}
                   -- djgpp, include/inline/pc.h
        int i = 0;
        __asm__("
          pushl %%eax\n
          movl %0, %%eax\n
          addl $1, %%eax\n
          movl %%eax, %0\n
          popl %%eax"
          :
          : "g" (i)
        );
          /* i++; */
                     -- tutorißl k assembleru djasm.html
SmutnΘ je, ╛e takov²ch p°φklad∙ je v╣ude habad∞j a tΘm∞° ka╛dß asm konstrukce, na kterou se podφvßm je ╣patn∞. Jß jsem napoΦφtal minimßln∞ 6 nedostatk∙ v t∞chto p°φkladech a co vy?

9.5 extern inline

Konstrukce extern inline konstrukce umo╛≥uje ud∞lat rychlΘ nßhra╛ky knihovnφch funkcφ. Pokud je zapnutß optimalizace a p°ekladaΦ narazφ na funkci deklarovanou jako extern inline, v╣echny dal╣φ volßnφ se inlinujφ. Pokud je ale optimalizace vyplß, funkce se ignoruje a volß se standardnφ. Proto do header∙ m∙╛ete sepsat svoje nejoblφben∞j╣φ funkce, kterΘ by m∞ly b²t rychlΘ a takov² header pak volat v╣ude, kde je t°eba. Takovß b∞╛nß extern inline funkce je:

extern inline void
outportb (unsigned short _port, unsigned char _data)
{
  asm volatile ("outb %1, %0"
          :
          : "d" (_port),
            "a" (_data));
}
Samoz°ejm∞, ╛e jde extern inline funkce pou╛φvat i pro standardnφ C k≤d, nejenom assembleru.

9.6 __builtin_constant_p

P°edstavte si, ╛e chcete implementovat optimßlnφ memset. To jde ud∞lat nap°φklad:

extern inline void * memset(void * s, char c,size_t count)
{
asm volatile(
       "cld
        rep
        stosb"
        : /* no output */
        :"a" (c),"D" (s),"c" (count)
        :"cx","di","memory","cc");
return s;
}
Φasem ale zjistφte, ╛e volßnφ memset(x,0,4096), kterΘ je ΦastΘ v jßd°e - nulujou se tφm strßnky - je neoptimßlnφ, proto╛e nuluje byte po bytu, p°esto, ╛e by to ╣lo hned po Φty°ech. Mnohem rychlej╣φ je:
extern inline void * memset(void * s, char c,size_t count)
{
asm volatile (
       "cld
        rep
        stosl"
        : /* no output */
        :"a" (c+(c<<8)+(c<<16)+(c<<24)),"D" (s),"c" (count/4)
        :"cx","di","memory","cc");
return s;
}
To sice nefunguje pro poΦty ned∞litelnΘ Φty°ma, ale jinak pracuje p∞kn∞. Ale zase m∙╛ou existovat volßnφ t°eba memset(s,0,4), pro kterΘ je pou╛itφ tohoto k≤du vrhßnφ atomov²ch nßlo╛φ na vrabce. Kdybychom mohli p°edpoklßt, ╛e count je konstanta, mohli bychom napsat nßsledujφcφ podivnou funkci:
extern inline void * memset(void * s, unsigned long pattern, size_t count)
{
        pattern=((unsigned char)patter) * 0x01010101;
        switch (count) {
                case 0:
                        return s;
                case 1:
                        *(unsigned char *)s = pattern;
                        return s;
                case 2:
                        *(unsigned short *)s = pattern;
                        return s;
                case 3:
                        *(unsigned short *)s = pattern;
                        *(2+(unsigned char *)s) = pattern;
                        return s;
                case 4:
                        *(unsigned long *)s = pattern;
                        return s;
        }
##define COMMON(x) \
asm ("cld; rep ; stosl" \
        x \
        : /* no outputs */ \
        : "a" (pattern),"c" (count/4),"D" ((long) s) \
        : "cx","di","memory","cc")

        switch (count % 4) {
                case 0: COMMON(""); return s;
                case 1: COMMON("\n\tstosb"); return s;
                case 2: COMMON("\n\tstosw"); return s;
                case 3: COMMON("\n\tstosw\n\tstosb"); return s;
        }

}
Toto funguje tak, ╛e optimizer, kter² u╛ bude ved∞t hodnotu count a pattern sßm p°edpoΦte nßsobenφ na zaΦßtku a vybere tu sprßvnou v∞tev ve switch. A tak tento memset bude fungovat velmi rychle pro v╣echny konstantnφ patterny a poΦty.

Jedin² problΘm je, ╛e bychom pot°ebovali memset pro konstantnφ parametry a memset pro nekonstantnφ a nutit programßtora, aby sßm dßval pozor na to, co je konstanta a co nenφ (n∞kdy to v∙bec nenφ jednoduchΘ, hlavn∞ kdy╛ parametr je sice prom∞nß, ale je mo╛nΘ p°edpoΦφtat jejφ hodnotu)

K tomu slou╛φ prßv∞ __builtin_constant_p. Ta vrßtφ 1, pokud parametr je konstanta a jinak 0. M∙╛eme tedy napsat memset pro konstantnφ a nekonstantnφ parametry a potom vybrat ten sprßvn² pomocφ:

#define __constant_c_x_memset(s, c, count) \
(__builtin_constant_p(count) ? \
 __constant_c_and_count_memset((s),(c),(count)) : \
 __constant_c_memset((s),(c),(count)))

#define __memset(s, c, count) \
(__builtin_constant_p(count) ? \
 __constant_count_memset((s),(c),(count)) : \
 __memset_generic((s),(c),(count)))

#define memset(s, c, count) \
(__builtin_constant_p(c) ? \
 __constant_c_x_memset((s),c,(count)) : \
 __memset((s),(c),(count)))
Zku╣enΘmu cΘΦka°i u╛ mo╛nß vstßvßjφ vlasy na hlav∞ a ptß se: a co memset(s,c++,count)? kolikrßt se c zv∞t╣φ? Ale ani on nemusφ mφt obavy - parametry funkce __builtin_constant_p se nevyhodnocujφ tak╛e i program:
main()
{
   int a = 1;
   __builtin_constant_p(a++);
   __builtin_constant_p(a++);
   printf("%i\n", a);
}
vypφ╣e jedna. Tento memset je plnohodnoutnou nßhra╛kou builtinu do p°ekladaΦe a mß tu v²hodu, ╛e ka╛d² jej m∙╛e upravovat podle sv²ch pot°eb - na velikost, rychlost, p°idat dal╣φ spec. p°ipady, pro r∙znΘ CPU apod.

Je nutnΘ poznamenat, ╛e __builtin_constant_p nepat°φ zrovna k nejspolehliv∞j╣φm. Jejφ hodnota se urΦuje p°ed propagacφ konstant (v tΘ dob∞ je u╛ nutnΘ v∞d∞t, kudy se program vydß) a tak i v p°edchozφm p°φpad∞ bude vracet 0, proto╛e parametrem je prom∞nß (p°esto, ╛e p°i propagaci konstant se p°ijde na to, ╛e jejφ hodnota je 1). Z toho d∙vodu nemß smysl pou╛φvat tuto funkci na parametry inline funkcφ a je nutnΘ psßt makra. Proto pou╛φvejte tutu metodu jen pokud to je nutnrΘ.

9.7 Attributy funkcφ

K optimalizaci se hodφ jakßkoliv informace. V n∞kter²ch p°φpadech je celkem t∞╛kΘ, aby optimizer zjistil n∞kterΘ speciality a proto mnoho p°ekladaΦ∙ mß mo╛nost p°idat k deklaracφm funkce i n∞kterΘ p°φdavnΘ attributy - nap°φklad to, ╛e funkce se nikdy nevrßtφ (exit) a tak nenφ t°eba po jejφm volßnφ dßle p°eklßdat. V∞t╣ina z nich to ale °e╣φ pomocφ konstrukce #pragma. To p°inß╣φ ΦastΘ potφ╛e s preprocesorem - nejde zahrnovat do maker apod. Proto╛e je #pragma neportabilnφ nelze tudφ╛ tyto v∞ci elegantn∞ p°idat do portabilnφch program∙.

GCC mß pon∞kud jinΘ °e╣enφ - pomocφ __attribute__, kter² se pφ╣e za deklaraci funkce. Nastavenφ takovΘho attributu potom vypadß:

void ahoj(void) __attribute__(const);
Pokud chcete mφt program p°enositeln², staΦφ p°idat do n∞jakΘho headeru konstrukci:
#ifdef __GCC__
# define CONST(f) f __attribute__const
#else
# define CONST(f) f
#endif
a potom pou╛φvat pouze:
void CONST(ahoj(void));
Jsou k dispozici nßsledujφcφ attributy:
noreturn

°φkß, ╛e funkce se nikdy nevrßtφ - jako nap°. exit.

const

funkce ned∞lß nic jinΘho, ne╛ ╛e se koukne na svΘ parametry a z nich vyvodφ v²sledek, bez dal╣φch vedlej╣φch efekt∙, nebo prohlφ╛enφ pam∞ti. Compiler potom d∞lß stejnΘ optimalizace jako na operßtor. P°φkladem takovych funkcφ je nap°φklad abs, sin, cos apod. Nap°φklad funkce rand to u╛ nenφ.

regparm(poΦet)

Prvnφch poΦet parametr∙ se bude p°edßvat v registru. To urychlφ volßnφ funkce. GCC umφ p°edßvat v registrech i defaultn∞, je ale nutnΘ pro to p°ekompilovat knihovny.

constructor

funkce se zavolß p°ed provedenφm main.

destructor

funkce se zavolß p°ed skonΦenφm programu.

stdcall

funkce bude pou╛φvat pascalßckΘ volacφ konvence.

cdecl

pou╛ijφ se C konvence, pokud pascalßckΘ volßnφ je jako default.

format(typ,format,odkud)

Funkce typu printf a scanf nemajφ ╛ßdnou kontrolu typ∙. To je zdrojem Φast²ch chyb a proto GCC umφ tyto typy kontrolovat. Pokud uvedete tento attribut u svΘ funkce, kterß mß stejnΘ konvence, GCC to bude kontrolovat i tam. Typ m∙╛e b²t printf nebo scanf.

unused

Compiler nebude vypisovat warning, kdyz funkce je neppou╛ita.

weak

Weak je n∞co jako static nebo global. Pokud v knihovn∞ ud∞lßte globßlnφ prom∞nou, je vid∞t zvenku a aplikace ji m∙╛e omylem p°epsat. Pokud je ale weak, je lokßlnφ pro danou knihovnu.

alias(fce)

Funkce je pouze alias pro funkci fce

section(jmeno)

Funkce se ulo╛φ do jinΘ sekce - linker potom m∙╛e tyto sekci dßt do jinΘ Φßsti v²sledku a tak jdou d∞lat v∞ci jako, ╛e na konci je inicializaΦnφ k≤d, kter² se po startu uvolnφ z pam∞ti.

9.8 N∞kterß roz╣φ°enφ ╣ikovnß (nejenom) pro psanφ maker

Celkem Φastno se stßvß, ╛e chcete napsat makro, kterΘ jde napsat na mφsto funkce, ale pot°ebujete tam prom∞nou, nebo smyΦku apod. a proto jako v²raz napsat nejde. Proto mß GCC {( )}, co╛ je konstrukce, kterß p°evede libovoln² blok do v²razu. Jeho hodnota je potom hodnota poslednφho vyhodnocenΘho v²razu. Proto jde napsat makro MAX tak, aby se parametry vyhodnocovaly jen jednou:

#define MAX(a,b) ({int _a=(a), _b=(b); _a > _b ? _a : _b })
Toto makro se chovß o n∞co lΘpe, ne╛ extern inline funkce se stejn²m k≤dem, proto╛e inline funkce se nap°ed optimalizujφ odd∞len∞ a po inlinovßnφ funkce se provedou u╛ jenom n∞kterΘ optimalizace a tak makra jsou stßle o n∞co lep╣φ pro optimizer.

TΘto konstrukce lze takΘ z v²hodou vu╛φt v kombinaci s asm:

#define plus(a,b) ({int _a=(a) _b=(b), asm("add %0 %1":"=g" (_a):\
                   "0" (_a):"g" (_b):"cc");_a}
KlasickΘ makro pro MAX mß sice nev²hodu, ╛e svΘ parametry vyhodnocuje n∞kolikrßt, ale zase funguje i pro jinΘ typy (t°eba pro double. K tomu aby i novß verze makra MAX chodila pro libovoln² typ slou╛φ konstrukce typeof, kterß nadefinuje typ podle expression danΘ jako parametr.
#define MAX(a,b) ({typeof _ta=(a), _tb=(b); \
                         _ta _a=(a); _tb _b(b); \
                         _a > _b ? _a : _b })
Dal╣φ ╣ikovnß v∞c je makro s prom∞n²m poΦtem parametru:
#define eprintf(f,a ...) fprintf(stderr, f, ## a);
N∞kterΘ zh²ralce mo╛nß napadlo psßt do makra labely. Ale nejde to, proto╛e kdy╛ pou╛ijete makro dvakrßt ve stejnΘ funkci, mßte tam i dva stejne labely a kompiler to neveme. Proto m∙╛ete na zaΦßtku bloku napsat __label = jmΘno a nadefinovat label jako lokßlnφ pro dan² blok.

9.9 Explicitnφ urΦovßnφ registr∙ pro prom∞nΘ

N∞kterΘ p°ekladaΦe umo╛≥ujφ urΦit registr pro prom∞nou tak, ╛e ji pojmenujete nap°φklad __ax. To ale p°inß╣φ problΘmy s portabilitou. Pokud chcete, aby na jednΘ architektu°e byla prom∞na v registru ax a na jinΘ v r1, musφte ji poka╛dΘ pojmenovat jinak. TakΘ hrozφ mo╛nost nßhodnΘ kolize.

GCC mß op∞t jin² p°φstup k v∞ci. Pokud chcete ulo╛it prom∞nou do registru napφ╣ete nap°φklad:

      register int ahoj asm("ax");
Gcc umφ i velice zajφmavou v∞c - d∞lat takovΘ prom∞nΘ globßln∞. To funguje docela dob°e, proto╛e b∞╛n∞ funkce potom co takovß prom∞nß byla deklarovßna registr nepou╛φvajφ a mohou tedy velmi rychle pracovat s danou prom∞nou. Knihovnφ funkce, kterΘ ale nic o tΘto prom∞nΘ nevφ, registr normßln∞ ulo╛φ na zßsobnφk a potom zase vyzvednou, tak╛e se jeho hodnota neztratφ. Nenφ p°φstupnß pouze v p°φpad∞, ╛e va╣e funkce je volßna z knihovnφ (qsort).


P°edchozφ Nßsledujφcφ Obsah

Dotazy a p°ipomφnky ohledn∞ strßnky posφlejte na hubicka@paru.cas.cz