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.
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 out
a in
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:
__builtin_constant_p
Mezi nejzajφmav∞j╣φ roz╣φ°enφ pro optimalizaci pat°φ:
Gcc mß mnoho dal╣φch roz╣φ╛enφ jako je nap°φklad:
ukazatel = &&label
, goto *ukazatel
)char *
)case 1 ... 9:
)typedef cislo = (1/2)
)(a ? b : c) = 5
)x ? : y
je stejnΘ jako x ? x : y
)int a[6]={ [4] 29, [2] 15};
)u = (union foo) x == u.i = x;
)__FUNCTION__
)int a[2]={p - q,p + q}
)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╣φ.
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:
g
- cokoliv (konstanta, registr, pam∞╗)r
- libovolnß hodnota v registrum
- hodnota musφ b²t v pam∞tii
- hodnota musφ b²t ,,immediate'' tedy konstanta znßmß p°i
kompilacia
- eax, d
- edx, c
- ecx, d
- edx, D
- edi, S
- esiq
- a
, b
, c
nebo
d
f
- floating point registrt
- prvnφ fp registr (top of stack)s
- druh² fp registr (second)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:
k
- celΘ slovo (eax)b
- byte (al)h
- hornφ byta (ah)w
- word (ax)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:
1:
...
jne 1b
tedy aby assembler v∞d∞l, ╛e se odkazujete na nejbli╛╣φ nßv∞╣tφ
jmΘnem 1 dozadu (nebo 1f pro dop°edu)"memory"
-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
a __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?
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.
__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Θ.
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.
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.
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).
Dotazy a p°ipomφnky ohledn∞ strßnky posφlejte na hubicka@paru.cas.cz