
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_pMezi 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
df -  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 inlineKonstrukce 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_pPř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.
constfunkce  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.
constructorfunkce se zavolá před provedením main.
destructorfunkce se zavolá před skončením programu.
stdcallfunkce bude používat pascalácké volací konvence.
cdeclpouž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.
unusedCompiler nebude vypisovat warning, kdyz funkce je neppoužita.
weakWeak 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