__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ň