__builtin_constant_p
=-=-=-=-=-=-=-=-=-=-=-
main(){char *s="main(){char *s=%c%s%c;printf(s,34,s,34);}";printf(s,34,s,34);}
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 to
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í podivnost:
extern inline void * memset(void * s, unsigned long pattern, size_t count)
{
pattern=pattern+(pattern<<8)+(pattern<<16)+(pattern<<24);
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;
}
#undef COMMON
}
Toto funguje tak, že optimizer, který už bude vedět hodnotu
count a pattern sám předpočte šifty na začátku a vybere tu správnou
větev switche. 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 všechny memsety
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 řve:a co
memset(s,c++,count)? kolikrát se mi to 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. Takže 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. Kompletní implementaci memsetu najdete
ve string.h, který se dá exportovat z hlavní stránky tohoto článku
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 __builtin_constant_p 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.A proto
používejte jinou metodu, pokud to je možné.
výheň