Když vývojář používající operační systém GNU/Linux použije sousloví „překladač céčka“, je téměř stoprocentně jisté, že má na mysli překladač programovacího jazyka C patřící do rodiny GCC (GNU Compiler Collection). Ovšem ve skutečnosti existuje pro Linux větší množství překladačů céčka. Mezi ně patří i zajímavý projekt Tiny C Compiler, jehož největší předností je velká rychlost překladu i možnost použít tento překladač ve funkci interpretru – je jím tedy možné céčkové programy přímo „spouštět“, podobně jako skripty psané v Perlu či BASHi.
Programovací jazyk C
Jedním z nejpopulárnějších v současnosti používaných programovacích jazyků je bezesporu programovací jazyk C navržený Dennisem Ritchiem. Jazyk C se postupně vyvinul z programovacích jazyků BPCL (autor Martin Richards, 1966) a B (autor Ken Thompson, 1970) až do současné podoby, která byla standardizována v normě ISO/IEC 9899:1999 známé pod zkráceným označením C99. Starší, dodnes velmi často používaný standard se jmenuje ISO 9899:1990, tento starší standard je prakticky shodný (až na jiné číslování jednotlivých paragrafů) s normou ANSI C (ANSI X3.159-1989 „Programming Language C“) a zkráceně se označuje C89 či méně často C90. Se všemi třemi zkratkami C89, C90 a C99 se ještě v tomto článku setkáme.
Obrázek 1: Známá fotografie Kena Thompsona a Dennise Ritchieho sedících před minipočítačem PDP. Podle počátečních písmen jejich příjmení se první de facto standardní syntaxe céčka označovala K&R. Mnoho překladačů dodnes syntaxi K&R podporuje, především z toho důvodu, že některé starší (stále udržované) zdrojové kódy jsou v K&R C napsány.
Programovací jazyk C je i přes absenci některých důležitých vlastností (například mu chybí automatický správce paměti či podpora silného typování a práce s objekty) využívaný jak pro tvorbu open source aplikací, tak i v čistě komerční oblasti – nejedná se jen o vývoj aplikací pro desktopy a servery, ale i pro mikrořadiče či digitální signálové procesory (DSP). Céčko je mnohdy využíváno i ve funkci cílového jazyka, do něhož se překládají programy zapsané v některých vyšších programovacích jazycích – vývojáři, kteří překladače těchto jazyků vytváří, se tak nemusí starat například o nízkoúrovňové optimalizace, protože je za ně již naprogramovali vývojáři překladače céčka. Z historického hlediska je zajímavé, že právě tímto způsobem vznikla první verze jazyka C++ (nástroj Cpre), i když moderní překladače C++ jsou již řešeny odděleně.
Obrázek 2: Dobová fotografie Kena Thompsona, který se podílel na vzniku jazyků B a C. Ken Thompson se později věnoval šachovým algoritmům. Vytvořil například databázi „perfektních“ koncovek a podílel se i na vzniku šachových partií mezi (vel)mistry a počítači.
To však není zdaleka vše, protože programovací jazyk C je dodnes důležitý i z toho důvodu, že jak rozhraní jader některých operačních systémů (Linux, Microsoft Windows i dalších systémů), tak rozhraní systémových knihoven bylo navrženo s ohledem na jmenné konvence céčka i s ohledem na možnosti jeho linkeru. V praxi to například znamená, že grafická knihovna OpenGL používá céčkové jmenné konvence i céčkové datové typy, což může uživatelům jiných jazyků, pro něž taktéž existuje podpora pro OpenGL, připadat poněkud neobvyklé. Totéž platí i pro knihovnu GTK+, která je založena na céčku, ale existují pro ni i různá více či méně kvalitní „objektově orientovaná“ rozšíření pro jiné programovací jazyky, například pro Python.
Obrázek 3: Grafy znázorňující vývoj popularity pěti nejpoužívanějších programovacích jazyků v projektech analyzovaných na serverech Ohloh. Z těchto grafů je patrné, že i když popularita céčka postupem času poněkud klesá (pravděpodobně ve prospěch dynamicky typovaných jazyků), stále je v něm napsáno enormní množství programového kódu.
Překladače programovacího jazyka C v Linuxu
Vývoj operačního systému GNU/Linux byl do značné míry ovlivněn, a možná bych si dokonce dovolil říci, že vůbec umožněn tím, že na něj byly portovány nástroje označované souhrnným názvem „GNU Compiler Collection (GCC)“. Tyto nástroje obsahují překladače několika programovacích jazyků, především pak překladače jazyků C, C++, Objective C, Java, Ada a Fortran. Kromě překladačů jsou součástí GCC i další nástroje, zejména preprocesor používaný jazyky C a C++, GNU assembler, linker, nástroj sledující pokrytí kódu testy (gconv) atd. Jedná se o velmi kvalitní nástroje, které navíc podporují obrovské množství různých platforem, operačních systémů a procesorových architektur, samozřejmě včetně procesorů řady x86 a x86_64.
Obrázek 4: Historie vývoje programovacího jazyka C i některých dalších programovacích jazyků, které jsou z céčka buď přímo odvozeny, nebo jsou alespoň inspirovány jeho syntaxí.
Kromě překladače céčka patřícího do skupiny nástrojů GCC (i samotný překladač se jmenuje GCC – GNU C Compiler, což může být poněkud matoucí, je tedy nutné vždy sledovat kontext, ve kterém se o GCC mluví či píše) je možné v operačním systému Linux použít i další překladače. Mezi ně patří například Clang z projektu LLVM, jenž je zajímavý především po technologické stránce. Dále se pak můžeme setkat s překladači komerčních firem, například překladačem vytvořeným společností Intel, který v případě některých typů optimalizací překonává GCC. V neposlední řadě je pak možné v Linuxu (a nutno říci, že nejenom v něm) použít překladač nazvaný „Tiny C Compiler“ (tcc), jehož popisem a porovnáním s GCC se budeme zabývat v následujících kapitolách.
Obrázek 5: Toto již dnes zcela jistě patří do historie vývoje výpočetní techniky a především pak operačních systémů – výpis části zdrojového kódu jádra Linuxu verze 0.01. Linus Torvalds pro vývoj jádra použil programovací jazyk C zkombinovaný s assemblerem, jak je ostatně ve světě Unixu zvykem.
Tiny C Compiler
Tiny C Compiler (tcc) je překladač programovacího jazyka C, který byl původně vytvořen Fabricem Bellardem a nyní se o jeho další vývoj a portaci na nové platformy stará komunita vývojářů, protože se samozřejmě jedná o open-source projekt. Tiny C Compiler v sobě kromě vlastního překladače obsahuje i linker, což znamená, že jeden binární program může sloužit jak pro překlad zdrojových textů (včetně preprocesingu) do objektového kódu, tak pro vytvoření výsledného spustitelného binárního programu. Všechny tři zmíněné funkce jsou implementovány v jediném spustitelném souboru, jehož velikost na platformě x86 nepřesahuje sto kilobajtů, což je například v porovnání s GCC zcela zanedbatelná velikost (dokonce i pouze GNU assembler je v binární podobě větší než celý tcc).
Tiny C Compiler podporuje standard C89/C90 i velkou část standardu C99, a to do té míry, že úpravy zdrojových kódů určených pro GCC většinou nejsou zapotřebí. Největší devizou překladače tcc je blesková rychlost překladu, protože vlastní překlad je jednoprůchodový. Na stránkách tohoto projektu se uvádí, že tcc je přibližně osmkrát rychlejší než překladač GCC (s použitím standardních voleb, tj. bez optimalizací), ovšem jak se můžete dozvědět z dalšího textu, může být tcc v extrémním případě rychlejší zhruba čtyřicetkrát! Na druhou stranu však tcc za většinou ostatních moderních překladačů céčka pokulhává v případě optimalizací prováděných při překladu. O tom, do jaké míry se absence většiny optimalizačních metod projeví na demonstračních příkladech (benchmarcích), si řekneme v navazujících kapitolách.
Obrázek 6: Aby se ze zdrojových textů napsaných v programovacím jazyku C získala binární spustitelná podoba aplikace, je provedeno několik činností, které jsou v klasických nástrojích (GCC) představovány samostatnými aplikacemi. Zdrojový text je nejdříve zpracován preprocesorem (cpp), potom je přeložen nástrojem cc1 (možným mezivýsledkem je zdrojový text v assembleru) a posléze jsou jednotlivé objektové soubory slinkovány (ld). Výsledkem je buď spustitelná aplikace, popř. Statická, či dynamická knihovna. V případě překladače Tiny C Compiler jsou tyto operace provedeny v jednom kroku.
Co to však znamená pro každodenní praxi vývojáře? Podle mého názoru je ideální zkombinovat vlastnosti tcc (doslova blesková rychlost překladu) a GCC (optimalizace) takovým způsobem, že pro samotný vývoj se použije překladač tcc a pro tvorbu výsledné binární podoby programu se naopak využije překladač GCC s příslušnými přepínači (-O3 atd.). Tiny C Compiler lze použít především na architekturách x86 a x86_64, i když existují i verze využitelné na procesorech ARM a DSP čipech řady TMS320C67xx. Všechna měření, jejichž výsledky jsou uvedeny v dalších kapitolách, byly provedeny na počítači s architekturou x86.
Získání, konfigurace a překlad Tiny C Compileru
Vzhledem k tomu, že překladač s Tiny C Compilerem již v dnešní době není součástí standardních balíčků Fedory, provedeme překlad a instalaci ručně, což však není nic složitého, protože se jedná o pouhých šest příkazů a tcc má pouze minimální závislosti. Nejprve je nutné získat tarball (archiv tar) obsahující zdrojové kódy poslední verze překladače:
wget http://download.savannah.nongnu.org/releases/tinycc/tcc-0.9.25.tar.bz2
Následně se zdrojové texty z archivu rozbalí:
tar xvfj tcc-0.9.25.tar.bz2
Měl by vzniknout nový adresář obsahující jak zdrojové texty, tak různé konfigurační skripty. Přejdeme tedy do nově vytvořeného adresáře:
cd tcc-0.9.25
Nyní již můžeme použít klasickou trojici příkazů pro konfiguraci překladu a vytvoření souboru Makefile, vlastní překlad a následnou instalaci:
./configure make sudo make install
A teď už zbývá jediné – ověřit si, že se Tiny C Compiler skutečně nainstaloval a lze ho spustit:
tcc -version tcc version 0.9.25
Programy použité pro porovnání Tiny C Compileru s GCC
Pro porovnání vlastností Tiny C Compileru a překladače céčka, jenž je součástí GCC, jsem zvolil tři diametrálně odlišné zdrojové kódy. Prvním zdrojovým kódem je kód projektu tth (TeX to HTML Translator), který je dostupný na adrese http://hutchinson.belmont.ma.us/tth/tth-noncom/download.html. Tento projekt je v několika ohledech extrémní, protože je uložen v jediném céčkovém zdrojovém kódu o velikosti 1172 kB, který má celkem 28 249 řádků! Překlad takto velkého souboru je pro překladač (přesněji řečeno pro překladač GCC) skutečně tvrdým oříškem, o čemž se ostatně přesvědčíme v další kapitole.
Druhým testovacím programem je generátor fraktálů, který při svém spuštění velmi intenzivně používá operace s hodnotami s plovoucí řádovou čárkou (floating point). V tomto programu se názorně ukáže, jak dobře provádí překladač různé optimalizace – jak optimalizace výpočtů, tak například rozbalení smyček. Kód tohoto programu je dostupný zde: fractal_renderer.c. Zde je k dispozici varianta se zvýrazněnou syntaxí: fractal_renderer.c.html.
Třetím příkladem je program pro řešení Sudoku. Algoritmus řešení je velmi jednoduchý, což však není v tomto případě podstatné, jelikož nás zajímá rychlost překladu a kvalita výsledného kódu. V tomto programu se velmi často používají operace pro práci s prvky dvourozměrných a trojrozměrných polí, takže se zde ukáže, jak dokáže překladač optimalizovat tento typ operací: sudoku_solver.c. Pro prohlédnutí zdrojového kódu tohoto příkladu můžete opět použít variantu se zvýrazněnou syntaxí: sudoku_solver.c.html.
Testujeme rychlost překladu
Překladač Tiny C Compiler je slovy svých tvůrců přibližně devětkrát rychlejší než překladač GCC, a to dokonce za předpokladu, že GCC nebude provádět žádné optimalizace (výchozím stavem je tedy přepínač -O0). O tom, do jaké míry je či není toto tvrzení pravdivé, se ale raději přesvědčíme sami. V následujících třech tabulkách jsou zobrazeny časy překladu pro všechny tři výše zmíněné demonstrační příklady. Nejprve byl překlad proveden s využitím Tiny C Compileru, posléze byl použit GCC bez dalších voleb a poslední dva překlady proběhly taktéž s využitím překladače GCC, ovšem v tomto případě byly použity volby -O3 (optimalizace na rychlost výsledného kódu) a -Os (optimalizace na velikost výsledného kódu).
Překlad aplikace „tth“:
Překlad | Čas překladu | TCC rychlejší |
gcc | 21,1 s | 42x |
gcc -Os | 113,2 s | 226x |
gcc -O3 | 137,9 s | 275x |
tcc | 0,5 s | - |
Graf 1: Časy překladu aplikace „tth“. Na vertikální osu je vynesen čas v sekundách.
Překlad demonstračního příkladu „fractal_renderer“:
Překlad | Čas překladu | TCC rychlejší |
gcc | 0,440 s | 7x |
gcc -Os | 0,604 s | 10x |
gcc -O3 | 0,933 s | 15x |
tcc | 0,060 s | - |
Graf 2: Časy překladu demonstračního příkladu „fractal_renderer“. Na vertikální osu je opět vynesen čas v sekundách.
Překlad demonstračního příkladu „sudoku_solver“:
Překlad | Čas překladu | TCC rychlejší |
gcc | 0,449 s | 12x |
gcc -Os | 0,896 s | 24x |
gcc -O3 | 9,261 s | 244x |
tcc | 0,038 s | - |
Graf 3: Časy překladu demonstračního příkladu „sudoku_solver“. Na vertikální osu je opět vynesen čas v sekundách.
Z tabulek je patrné, že tvůrci Tiny C Compileru jsou ve svých tvrzeních o rychlosti svého překladače ještě zbytečně skromní, protože tcc dokázal přeložit aplikaci „tth“ zhruba čtyřicetkrát rychleji než gcc! Samozřejmě se jedná o dosti mezní případ, který však názorně ukazuje některé přednosti jednoprůchodového překladu (zdá se, že v tomto případě doba překladu roste pouze lineárně s počtem překládaných řádků). U dalších dvou demonstračních příkladů nejsou rozdíly tak patrné (zrychlení je „pouze“ sedminásobné, popř. dvanáctinásobné), a to zejména proto, že se jedná o relativně malé projekty, u nichž se ještě naplno neprojeví rychlost interní překladové smyčky tcc, protože je nutné provést závěrečné slinkování, které je zhruba stejně rychlé (zpracovávají se tytéž sdílené i statické knihovny atd.).
Vliv (absence) optimalizací na rychlost výsledných programů
Další věc, která nás při porovnávání různých překladačů céčka zajímá, je kvalita výsledného spustitelného binárního kódu. Zde je zřejmé, že Tiny C Compiler nemůže dosahovat kvalit GCC, zejména při povolení všech optimalizací, které GCC podporuje. Ovšem zajímavé bude zjistit a vzájemně porovnat kód vygenerovaný Tiny C Compilerem s kódem, který byl vytvořen překladačem GCC bez zapnutí optimalizací. V ideálním případě by kódy mohly být srovnatelně rychlé, ale opět se o tom raději přesvědčíme změřením doby běhu dvou demonstračních příkladů – „fractal_renderer“ a „sudoku_solver“. Výsledky měření, které byly provedeny na 32bitové platformě s mikroprocesorem Athlon T-Bird (900 Mhz), můžete vidět v následující dvojici tabulek:
Překlad | Doba běhu (v sekundách) | Relativní rychlost oproti tcc |
gcc | 52 | 117 % |
gcc -Os | 36 | 169 % |
gcc -O3 | 35 | 174 % |
tcc | 61 | 100 % |
Graf 4: Doba běhu demonstračního programu „fractal_renderer“ přeloženého různými překladači s různými volbami.
Překlad | Doba běhu (v sekundách) | Relativní rychlost oproti tcc |
gcc | 54 | 115 % |
gcc -Os | 21 | 295 % |
gcc -O3 | 14 | 443 % |
tcc | 62 | 100 % |
Graf 5: Doba běhu demonstračního programu „sudoku_renderer“ přeloženého různými překladači s různými volbami.
Z předchozích dvou tabulek a grafů vychází, že kód generovaný překladačem Tiny C Compiler je sice na 32bitové platformě x86 nejpomalejší, ovšem rozdíl oproti neoptimalizovanému kódu generovanému s využitím GCC není nijak závratně velký. Když navíc přihlédneme k dobám kompilace, kde tcc jasně vyhrává, je patrné, že Tiny C Compiler není jen další projekt na hraní, ale reálně a kvalitně naprogramovaný nástroj.
Závěrem
Cílem Tiny C Compileru zajisté není konkurovat ve všech oblastech překladači céčka, který je ústřední součástí nástrojů GCC, ovšem v některých případech může být jeho použití výhodné. TCC je možné použít především při vývoji, protože rychlost překladu je tak velká, že u menších aplikací (řádově stovky až tisíce řádků) vlastně ani není nutné program dělit do více modulů a pro překlad používat soubory Makefile (toto rozdělení je samozřejmě užitečné, ovšem mnohdy se již při vývoji provede takový návrh modulů, který se posléze ukáže být nepříliš vhodný). Tiny C Compiler také může mít své opodstatnění při tvorbě „přímo spustitelných C programů“, protože i bez provádění optimalizací je výsledkem práce TCC vždy nativně spustitelný kód, který je proveden mnohem rychleji než různé verze bajtkódu (Python, částečně i Java) či přímo interpretovaného kódu (BASH) – této možnosti se budeme věnovat příště.
27. 8. 2012 at 13:24
Excelentní. Ostatně jako jiné tvoje články 😉
28. 8. 2012 at 11:05
Diky 🙂
27. 8. 2012 at 15:34
„Totéž platí i pro knihovnu GTK+, která je založena na céčku, ale existují pro ni i různá více či méně kvalitní „objektově orientovaná“ rozšíření pro jiné programovací jazyky“
Mno zrovna GTK+ je objektově orientovaná a spoustu věcí má řešenou stejně jako Python. Že je to vše zapsáno v C, je jiná věc :).
27. 8. 2012 at 20:42
GTK+ mě popravdě řečeno připadne jako parodie na OOP, což má co dělat s tím, že je psaná právě v céčku. Ale jinak nic proti céčku, jen se na něj někdy snažíme napasovat paradigmata, pro něž nebylo navrženo (OOP, dokonce i fukncionální paradigma)
27. 8. 2012 at 21:32
„GTK+ mě popravdě řečeno připadne jako parodie na OOP“
Mně to naopak přijde jako skvělá ukázka implementace OOP.
„Ale jinak nic proti céčku, jen se na něj někdy snažíme napasovat paradigmata, pro něž nebylo navrženo“
Python je taky v céčku a neviděl jsem, že by si na to někdo stěžoval.
29. 8. 2012 at 09:22
No ale to je prece rozdil mezi *jazykem* napsanym v cecku a *knihovnou* kterou musi ceckovej programator volat primo z neobjektoveho cecka. Me cely GTK+ i s jeho GObject atd. pripadne jako pokus dodat napriklad do stareho Fortranu podporu pro rekurzivni funkce – taky to dost drelo.
27. 8. 2012 at 15:36
„Grafy znázorňující vývoj popularity pěti nejpoužívanějších programovacích jazyků“
Jak se tam pak dostaly XML a HTML?
28. 8. 2012 at 11:08
Na serveru Ohloh se pri analyze kodu pocitaji XML, HTML ale napriklad i pocet commitu a commitlych radku pro Makefile atd.
27. 8. 2012 at 15:52
Chtěl jsem zkusit, jestli s tím půjde zkompilovat něco, co se běžně kompiluje s GCC…
checking for gcc… /home/pavlix/tmp/tccbuild/bin/tcc
checking whether the C compiler works… no
configure: error: in `/home/pavlix/oss/NetworkManager‘:
configure: error: C compiler cannot create executables
Máš tušení, co je k tomu potřeba?
27. 8. 2012 at 16:04
$ tcc examples/ex1.c
tcc: file ‚/usr/lib/crt1.o‘ not found
tcc: file ‚/usr/lib/crti.o‘ not found
tcc: file ‚/usr/lib/crtn.o‘ not found
tcc: undefined symbol ‚printf‘
27. 8. 2012 at 16:08
Už to mám, jde o /usr/lib versus /usr/lib64. Pojedu vlakem, tak to zkusím patchnout :).
27. 8. 2012 at 20:45
Pokud v tom vlaku nebudeš strojvedoucím, tak směle do toho 🙂
27. 8. 2012 at 21:28
Ve vlaku jsem vedl jen svůj vlastní stroj a problémy s lib64 jsem vcelku v pohodě vyřešil… tcc už kompiluje. Bohužel nelinkuje (undefined symboly z libc).
Takže pro projekty v autotools, které nerozlišují kompilátor a linker to moc použitelné ani otestovatelné není.
27. 8. 2012 at 21:38
Dva roky starý bugreport na nongnu.org:
http://savannah.nongnu.org/bugs/?30457
28. 8. 2012 at 13:21
Chapu to spravne tak, ze se kompilace provadi pres tcc a slinkovani pres ld? Nebo vse resi tcc a nenajde v text. sekci printf symbol?
28. 8. 2012 at 20:17
Prostě je tcc zabugovaný a ani po drobných úpravách nezvládne linkovat na x86_64.
29. 8. 2012 at 00:54
Popřípadě může být ještě problém v glibc, abych zbytečně nekřivdil.
29. 8. 2012 at 09:25
Zajimave, ale mozna je to tim, ze x86_64 je relativne nova vec pro tcc, puvodne to bylo pro 32bit Intely.
Me to na i386 to funguje prozatim dobre a hlavne *rychle* 🙂
Mam jen jeden problem s programem pouzivajicim OpenGL, ale to taky muze byt zpusobeno tim, ze se v headerech pouzivaji divny konstrukce, ktere muzou (ale nemusi – musim jeste prozkouset) byt mimo ANSI.
x86_64 nekde vyzkoumam taky, ale tady na starickem Athlonu ne:/
29. 8. 2012 at 09:28
Jako kdyby se nám povedlo opravit ty chyby
na x86_64 a dát tomu nějaký buildsystém, aby to vůbec mělo šanci trochu fungovat v distribucích…
Ono totiž nepodporovat DESTDIR a nepodporovat lib64 je docela prasárna.
29. 8. 2012 at 09:41
Mám na tebe přecijen ještě prosbu, jestli bys nemohl dát dohromady postup, jak to můžu vyzkoušet alespoň pro 32bit na 64bit distribuci.
Jako kus už mám hotový:
$ sudo yum install glibc-devel.i686
$ CFLAGS=-m32 ./configure
$ make
$ sudo make install
$ tcc examples/ex1.c
/usr/lib/crt1.o: invalid object file
/usr/lib/crti.o: invalid object file
/lib/libc.so.6: bad architecture
/usr/lib/crtn.o: invalid object file
tcc: undefined symbol ‚printf‘
$ file /usr/lib/crt1.o
/usr/lib/crt1.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.32, not stripped
$ file -L /lib/libc.so.6
/lib/libc.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (GNU/Linux), dynamically linked (uses shared libs), BuildID[sha1]=0x5e0f6cdb0b4c5c6dcc6c1a79ffa29eb4f0981d3a, for GNU/Linux 2.6.32, not stripped
Těžko říct, co se mu nelíbí.
29. 8. 2012 at 09:42
Původně to mělo být nové vlákno a prosba na Pavla, ale kdo tušíte, jak to zlomit, dejte vědět :).
27. 8. 2012 at 16:35
Dobrý článok.
Pochutil som si.
28. 8. 2012 at 10:31
„Známá fotografie Kena Thompsona a Dennise Ritchieho sedících před minipočítačem PDP. Podle počátečních písmen jejich příjmení se první de facto standardní syntaxe céčka označovala K&R. Mnoho překladačů dodnes syntaxi K&R podporuje, především z toho důvodu, že některé starší (stále udržované) zdrojové kódy jsou v K&R C napsány.“
1. Nepřijde mi, že by oba seděli 🙂
2. K&R není podle dvojice Thompson & Ritchie – to K tam moc neštimuje 😉 K je tam od Briana Kernighana, viz. např. https://en.wikipedia.org/wiki/The_C_Programming_Language_%28book%29
28. 8. 2012 at 11:11
Hmm popisky u obrazku se rozhodily, omlouvam se :/