Obsah
1. Kombinace programovacího jazyka C3 s assemblerem
2. Krátké ohlédnutí do minulosti
4. Assemblery vestavěné do vyšších programovacích jazyků
5. Assembler a programovací jazyk C
6. Assembler a programovací jazyk C3
7. Instrukce assembleru posílané přímo do výstupu pro backend překladač
8. Přístup k lokálním proměnným bez specifikace jejich jména
9. Kontrola, jakým způsobem byl překlad proveden – přepínač –emit-asm
10. Blok asm zabudovaný přímo do jazyka C3
11. Realizace volání služby operačního systému přes blok asm
12. Přístup k lokálním proměnným z bloku asm s využitím jejich jména
13. Jakým způsobem se předávají parametry do funkcí a jak se z nich vrací hodnoty?
14. Implementace celé funkce ve vestavěném assembleru?
15. Volání funkcí naprogramovaných v „externím“ assembleru
16. Krátké zopakování: volání externích funkcí naprogramovaných v jazyku C
17. Zavolání funkce naprogramované v assembleru z jazyka C3
19. Repositář s demonstračními příklady
1. Kombinace programovacího jazyka C3 s assemblerem
V dnešní části seriálu o programovacím jazyku C3 se budeme zabývat tím, jakým způsobem je možné zkombinovat zdrojový kód v jazyku C3 s assemblerem. Jedná se o jednu z vlastností programovacího jazyka C3, která je zmíněna na domovské stránce tohoto jazyka jako jedna ze stěžejních technologií, která jazyk C3 odlišuje od standardního jazyka C.
Je ovšem nutné poznamenat, že tuto technologii v praxi využijí s velkou pravděpodobností pouze někteří vývojáři, a to navíc pouze ve specifických situacích (ruční vektorizace kódu, tvorba kodeků popř. subrutin pro zpracování signálů, volání specifických instrukcí typu generování náhodných čísel, kryptografických operací atd.). Na druhou stranu se ovšem jedná o téma, které velmi dobře odhaluje činnost dalších pomocných nástrojů, které jsou nedílnou součástí ekosystému tohoto programovacího jazyka. Zabývat se totiž budeme hned několika způsoby, jakými je možné zkombinovat jazyk C3 s assemblerem.
2. Krátké ohlédnutí do minulosti
Assemblery za sebou mají velmi dlouhý vývoj, protože první nástroje, které se začaly tímto názvem označovat, vznikly již v padesátých letech minulého století, a to na mainframech vyráběných společností IBM i jejími konkurenty (což byly firmy UNIVAC, Burroughs, Honeywell, General Electric atd.). Před vznikem skutečných assemblerů byla situace poněkud složitá. První aplikace pro mainframy totiž byly většinou programovány přímo ve strojovém kódu, který bylo nutné přímo zadávat z takzvaného řídicího panelu (control panel) počítače nebo načítat z externích paměťových médií (tedy v době vládnutí těchto mainframů: z děrných štítků, magnetických pásek atd.).
Programátoři, co nesnášíte BS, ale máte rádi business! Y Soft je česká firma s globálním dopadem (100+ zemí, 1M+ uživatelů a >100% meziroční růst). R&D úplně bez manažerů (130 developerů). Otevíráme 30 pozic pro Cloud a AI: Praha/Brno/Ostrava/remote. Zodpovědnost ano, mikro-management ne. Pojď někam, kde můžeš věci změnit.
Ovšem zapisovat programy přímo ve strojovém kódu je pochopitelně zdlouhavé, vedoucí k častým chybám a pro větší aplikace z mnoha důvodů nepraktické, o čemž se ostatně mohli relativně nedávno přesvědčit například i studenti programující na československém mikropočítači PMI-80 (na druhou stranu se ovšem jednalo o vynikající učební pomůcku).
Z důvodu usnadnění a zrychlení práce vývojářů a pro snížení počtu chyb (oprava chyby, resp. další iterace vývoje, byla velmi drahá a zdlouhavá) tedy vznikly první utility, jejichž úkolem bylo transformovat programy zapsané s využitím symbolických jmen strojových instrukcí do (binárního) strojového kódu určeného pro konkrétní typ počítače a jeho procesoru.
Obrázek 1: Kód v assembleru je možné, pochopitelně pokud to daný assembler umožňuje, psát i strukturovaně, používat subrutiny a funkce atd. Zde se konkrétně jedná o assembler pro slavné osmibitové mikroprocesory MOS 6502.Těmto pomocným programům, jejichž možnosti se postupně vylepšovaly (například do nich přibyla podpora textových maker, řízení víceprůchodového překladu, vytváření výstupních sestav s překládanými symboly, později i skutečné linkování s knihovnami atd.), se začalo říkat assemblery a jazyku pro symbolický zápis programů pak jazyk symbolických instrukcí či alternativně jazyk symbolických adres – assembly language (někdy též zkráceně nazývaný assembler, takže toto slovo má vlastně dodnes oba dva významy). Jednalo se o svým způsobem převratnou myšlenku: sám počítač byl totiž použit pro tvorbu programů, čímž odpadla namáhavá práce s tužkou a papírem. Posléze se zjistilo, že i programování přímo v assembleru je většinou pracné a zdlouhavé, takže se na mainframech postupně začaly používat různé vyšší programovací jazyky, zejména FORTRAN a COBOL. Použití vyšších programovacích jazyků bylo umožněno relativně vysokým výpočetním výkonem mainframů i (opět relativně) velkou kapacitou operační paměti; naopak se díky vyšším programovacím jazykům mohly aplikace přenášet na různé typy počítačů, což je nesporná výhoda.
Oživení zájmu o programování v assembleru přinesl vznik minipočítačů (například známé řady PDP) a na konci sedmdesátých let minulého století pak zcela nového fenoménu, který nakonec přepsal celé dějiny výpočetní techniky – domácích osmibitových mikropočítačů. Na osmibitových domácích mikropočítačích se používaly dva typy assemblerů. Prvním typem byly assemblery interaktivní, které uživateli nabízely poměrně komfortní vývojové prostředí, v němž bylo možné zapisovat jednotlivé instrukce v symbolické podobě, spouštět programy, krokovat je, vypisovat obsahy pracovních registrů mikroprocesoru atd.
Velkou výhodou byla nezávislost těchto assemblerů na rychlém externím paměťovém médiu (například disketové jednotce), který mnoho uživatelů a programátorů ani nevlastnilo. Druhý typ assemblerů je používán dodnes – jedná se vlastně o běžné překladače, kterým se na vstupu předloží zdrojový kód (uložený na kazetě či disketě) a po překladu se výsledný nativní kód taktéž uloží na paměťové médium (odkud ho lze následně spustit). Tyto assemblery byly mnohdy vybaveny více či méně dokonalým systémem maker (odtud ostatně pochází i označení macroassembler).
Assemblery byly mezi programátory poměrně populární i na osobních počítačích Amiga a Atari ST, a to i díky tomu, že instrukční kód mikroprocesorů Motorola 68000 byl do značné míry ortogonální, obsahoval relativně velké množství registrů (univerzální datové registry D0 až D7 a adresové registry A0 až A7) a navíc bylo možné používat i takové adresovací režimy, které korespondovaly s konstrukcemi používanými ve vyšších programovacích jazycích (přístupy k prvkům polí, přístup k lokálním proměnným umístěných v zásobníkovém rámci, autoinkrementace adresy atd.).
3. Assemblery v Linuxu
V této kapitole budeme pod termínem „assembler“ chápat programový nástroj určený pro transformaci zdrojového kódu naprogramovaného v jazyku symbolických adres do strojového kódu. Pro Linux vzniklo hned několik takových nástrojů, přičemž některé nástroje jsou komerční a jiné patří mezi open source. Z nekomerčních nástrojů, které nás samozřejmě zajímají především, se jedná o známý GNU Assembler, dále pak o nástroj nazvaný Netwide assembler (NASM), nástroj Yasm Modular Assembler či až překvapivě výkonný vasm. NASM a Yasm jsou pro první krůčky v assembleru velmi dobře použitelné, neboť mají dobře zpracovaný mechanismus reakce na chyby, dají se v nich psát čitelné programy atd. Určitý problém nastává v případě, kdy je nutné vyvíjet aplikace určené pro jinou architekturu, než je i386 či x86_64, a to z toho důvodu, že ani Netwide assembler ani Yasm nedokážou pracovat s odlišnou instrukční sadou. Naproti tomu GNU Assembler tímto problémem ani zdaleka netrpí, ovšem zápis assembleru se pro každou architekturu odlišuje (což se například týká i zápisu poznámek atd.).
GNU Assembler (gas) je součástí skupiny nástrojů nazvaných GNU Binutils. Jedná se o nástroje určené pro vytváření a správu binárních souborů obsahujících takzvaný „objektový kód“, dále nástrojů určených pro práci s knihovnami strojových funkcí i pro profilování. Mezi GNU Binutils patří vedle GNU Assembleru i linker ld, profiler gprof, správce archivů strojových funkcí ar, nástroj pro odstranění symbolů z objektových a spustitelných souborů strip a několik pomocných utilit typu nm, objdump, size a strings. GNU Assembler je možné použít buď pro překlad uživatelem vytvořených zdrojových kódů nebo pro zpracování kódů vygenerovaných překladači vyšších programovacích jazyků (GCC atd.). Zajímavé je, že všechny moderní verze GNU Assembleru podporují jak původní AT&T syntaxi, tak i (podle autora článku čitelnější) syntaxi používanou společností Intel.
Netwide Assembler (NASM) vznikl v době, kdy začali na operační systém Linux přecházet programátoři znající operační systémy DOS a (16/32bit) Windows. Tito programátoři byli většinou dobře seznámeni s možnostmi assemblerů, které se na těchto platformách používaly nejčastěji – Turbo Assembleru (TASM) společnosti Borland i Microsoft Macro Assembleru (MASM) a tak jim možnosti GNU Assembleru (který má své kořeny na odlišných architekturách) příliš nevyhovovaly. Výsledkem snah o vytvoření nástroje podobnému TASMu či MASMu byl právě NASM, který podporuje stejný způsob zápisu operandů instrukcí a navíc ještě zjednodušuje zápis těch instrukcí, u nichž je jeden operand tvořen nepřímou adresou. NASM byl následován projektem Yasm (fork+přepis), ovšem základní vlastnosti a především pak vazba na platformu i386 a x86_64 zůstaly zachovány (to mj. znamená, že například na Raspberry Pi možnosti těchto dvou nástrojů plně nevyužijeme, což je určitě škoda; situace je o to zajímavější, že původní autor NASMu nyní pracuje pro společnost ARM).
4. Assemblery vestavěné do vyšších programovacích jazyků
Jazyk C3 podporuje takzvaný inline assembler, tj. kombinaci zápisu zdrojového kódu jak v jazyku C3, tak i v assembleru. Ve skutečnosti se nejedná o žádný nový ani přelomový koncept, protože s touto technologií můžeme pracovat již téměř čtyřicet let. V této kapitole si ukážeme, jak tento problém řeší různé (historické i současné) programovací jazyky.
Velmi důležitým dialektem BASICu, který byl velmi úspěšný především v západních zemích (ovšem nikoli v ČSSR, kde panovala, podobně jako v dalších státech RVHP, poněkud specifická situace), je BBC BASIC, jehož původním autorkou je Sophie Wilsonová a mezi další autory, kteří například provedli portaci tohoto programovacího jazyka na jiné platformy, patří i známý Richard Russell, který stále o tomto jazyku udržuje stránky dostupné na adrese http://www.bbcbasic.co.uk/index.html (z této stránky si můžete stáhnout i variantu BBC BASICu doplněnou o podporu knihovny SDL 2 a dostupnou i na Linux. K dispozici je ovšem i čistě terminálová varianta určená opět pro Linux a mnohé další varianty (Windows apod.).
Mezi zajímavou (a v tomto případě i poměrně vzácnou a v jiných dialektem BASICu málo viděnou) vlastnost programovacího jazyka BBC BASIC patří možnost kombinace programu zapsaného v BASICu s částmi zapisovanými v assembleru, tj. v nízkoúrovňovém jazyku symbolických instrukcí odpovídajícímu (binárním) strojovým instrukcím použitého mikroprocesoru. Tato pro mnohé programátory důležitá vlastnost byla do BBC BASICu přidána ještě v dobách, kdy byl tento jazyk vyvíjen pro osmibitové mikroprocesory MOS 6502, ovšem později, spolu s poměrně rychlým rozšiřováním BBC BASICu na další platformy, byla přidána i podpora pro symbolické instrukce mikroprocesoru Intel 8080, Zilog Z80 a dalších osmibitových a o několik let později i šestnáctibitových a třicetidvoubitových mikroprocesorů, zejména úspěšných mikroprocesorových řad Intel 80×86 a Motorola M68000 používaných v mnoha osobních počítačích (zapomenout ovšem nesmíme ani na platformu ARM!).
Ve fragmentu programu, který je vypsán pod tímto odstavcem je ukázán způsob současného použití BASICu a assembleru osmibitového mikroprocesoru Zilog Z80. Povšimněte si především použití hranatých závorek pro označení části programu napsaného v assembleru i toho, že v assembleru se poznámky i odkazy pro skoky zapisují odlišným způsobem než v BASICu (v assembleru se používají textová navěští – labely, zatímco v BASICu čísla řádků – v tomto ohledu je assembler vysokoúrovňovějším jazykem než samotný BASIC):
100 DIM code 15 :REM Reserve space for 16 bytes of code 110 bdos=5 120 FOR pass=0 TO 1 :REM Implement 2-pass assembly 130 P%=code :REM Set program counter at start of each pass 140 [OPT pass*3 ;Enter assembler and select listing on pass 2 150 LD D,95:LD E,ASC"!" 160 .loop ;A label 170 LD C,2 ;Source statements 180 PUSH DE:CALL bdos:POP DE 190 INC E:DEC D:JR NZ,loop 200 RET:] :REM Exit assembler 210 NEXT pass 220 CALL code :REM Execute assembly language routine
Podpora pro kombinaci kódu psaného ve vyšším programovacím jazyce s assemblerem se objevila i ve slavném Turbo Pascalu. Ten podporoval speciální blok asm-end, přičemž vestavěný assembler dokázal pracovat s lokálními i globálními proměnnými atd. Dokonce bylo možné psát i celé funkce v assembleru. Krátká ukázka:
var
I : Integer;
begin
I:=3;
asm
movl I,%eax
end;
end;
Příklad celé funkce psané v assembleru. Tato funkce přistupuje ke globální proměnné nazvané OLDMODE:
5. Assembler a programovací jazyk C
Programovací jazyk C je poměrně často označován termínem „přenositelný assembler“. Je tomu tak z toho důvodu, že je možné relativně snadno odhadnout, jakým způsobem jsou jednotlivé jazykové konstrukce překládány do strojového kódu (což ovšem v moderních překladačích s pokročilými optimalizacemi už není tak jednoznačné). Nicméně jazyk C obsahuje jen takové konstrukce, které mají svůj jednoznačný „obraz“ ve strojovém kódu. To však ale neznamená, že C plně nahrazuje assembler – tak tomu není a ani nemůže být, protože některé konstrukce jsou nejlépe realizovány právě v assembleru (a postupy využívající intrinsic atd. situaci řeší jen částečně). Z tohoto důvodu má kombinace assembleru a C dosti dlouhou tradici.
Příkladem může být dnes již historický jazyk Turbo C nebo Borland C (C++). Ten obsahoval podporu pro blok asm, podobně jako výše zmíněný Turbo Pascal pocházející od stejné společnosti (Borland). Příkladem může být blok zapsaný v assembleru, který v DOSu přepne grafický režim přes službu BIOSu. Po přepnutí do grafického režimu s rozlišením 320×200 pixelů se celá obrazovka vyplní barvovým vzorkem:
#include <dos.h>
#include <stdio.h>
int main(void) {
unsigned char far *ptr = (unsigned char*)MK_FP(0xa000, 0000);
unsigned int i;
printf("%p\n", ptr);
getch();
asm {
mov ah, 00h
mov al, 13h
int 10h
}
for (i=0; i<(unsigned)(320*200); i++) {
*ptr++ = i;
}
getch();
return 0;
}
Některé překladače jazyka C namísto bloku asm podporují odlišné způsoby kombinace C s assemblerem. Příkladem je překladač CC65, ve kterém je možné zapisovat jednotlivé instrukce tak, jakoby se jednalo o volání (pseudo)funkce nazvané __asm__, které se v prvním parametru předává řetězec se jménem instrukce i s jejími parametry. Zajímavé ovšem je, že této pseudofunkci je možné předávat například obsahy proměnných atd.
unsigned char a;
unsigned char b;
unsigned char c;
void main(void)
{
__asm__ ("lda %v", a);
__asm__ ("clc");
__asm__ ("adc %v", b);
__asm__ ("sta %v", c);
}
Společně s rozvojem překladačů, které dokážou provádět i poměrně složité optimalizace, se (možná poněkud paradoxně) možnosti kombinace jazyka C s assemblerem zkomplikovala. Je tomu tak z toho důvodu, že překladač již musí mít informaci o tom, jaké registry jsou v assemblerovském bloku modifikovány, které hodnoty je možné zahodit, které datové struktury byly modifikováno atd. Proto například GNU C sice podporuje kombinaci céčka s assemblerem, ale samotný zápis je již složitější – programátor musí překladači různým způsobem napovídat. Na druhou stranu může překladač sám použít vhodné pracovní registry atd. Příklad zápisu, ve kterém je určeno, že proměnná src bude v assemblerovském bloku čtena, zatímco do proměnné dst se bude provádět zápis, vypadá takto:
int main(void) {
int src = 1;
int dst;
asm ("mov %1, %0\n\t"
"add $1, %0"
: "=r" (dst)
: "r" (src));
printf("%d\n", dst);
}
Překlad funkce main do assembleru dopadne následovně. Zvýrazněny jsou instrukce, které odpovídají bloku asm:
400466: 55 push rbp 400467: 48 89 e5 mov rbp,rsp 40046a: 48 83 ec 10 sub rsp,0x10 40046e: c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1 400475: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 400478: 89 c0 mov eax,eax 40047a: 83 c0 01 add eax,0x1 40047d: 89 45 f8 mov DWORD PTR [rbp-0x8],eax 400480: 8b 45 f8 mov eax,DWORD PTR [rbp-0x8] 400483: 89 c6 mov esi,eax 400485: bf 80 11 40 00 mov edi,0x401180 40048a: b8 00 00 00 00 mov eax,0x0 40048f: e8 dc fe ff ff call 400370 <printf@plt> 400494: b8 00 00 00 00 mov eax,0x0 400499: c9 leave 40049a: c3 ret
6. Assembler a programovací jazyk C3
Nyní se již konečně dostáváme k programovacímu jazyku C3, konkrétně k jeho podpoře spolupráce s assemblerem. Současná verze překladače jazyka C3 podporuje tři způsoby kooperace s assemblerem:
- Jednotlivé instrukce assembleru lze zapisovat do pseudofunkceasm, tedy podobně, jako tomu bylo v případě překladače CC65. Ovšem interně se zapisované instrukce přímo posílají do backend překladače (LLVM) bez toho, aby docházelo k jakémukoli zpracování na straně překladače jazyka C3. Nedochází tedy ani ke kontrole chyb, ani například k vyhodnocení jmen proměnných atd. Co je ale horší – i samotný zápis instrukcí a jejich operandů je poplatný LLVM a tudíž dosti nečitelný.
- Dále je možné použít blok asm (nikoli pseudofunkci), což je koncept, který jsme mohli vidět použitý v Turbo Pascalu i v Turbo/Borland C/C++. I přes některá omezení se jedná o nejlepší způsob, protože lze snadno pracovat s céčkovskými proměnnými, jejich adresami atd. I samotný způsob zápisu instrukcí je poměrně dobře čitelný, jak si ostatně ukážeme na demonstračních příkladech.
- Poslední způsob vlastně již známe – kód v assembleru je překládán odděleně od kódu zapisovaného v jazyku C3. Posléze se využije schopnosti C3 přímo volat nativní kód bez nutnosti použití technologií typu FFI. Předností je možnost využití jakéhokoli assembleru, oddělení těch částí kódu, které jsou závislé na architektuře, od obecného kódu atd. Nevýhodou pak složitý přístup k proměnným C3, ovšem tímto způsobem se v assembleru většinou definují celé funkce a nikoli menší bloky.
7. Instrukce assembleru posílané přímo do výstupu pro backend překladač
Podívejme se nejdříve na první způsob, který byl zmíněn v předchozí kapitole, tedy na zápis pseudofunkce asm, které se v řetězci předávají instrukce posílané přímo do LLVM (samotný překladač C3 už neprovádí žádné kontroly). Pokusme se například implementovat volání služby jádra pro ukončení procesu. V 32bitovém režimu x86 se zavolá funkce číslo 1, jejímž parametrem je návratový kód. Číslo funkce je předáno v registru EAX, návratový kód v registru EBX a samotné vyvolání služby jádra zajistí INT 80:
module assembly;
import std::io;
fn void main()
{
io::printn("About to exit");
asm("movl $$1, %eax");
asm("movl $$42, %ebx");
asm("int $$0x80");
io::printn("I'm still alive...");
}
V 64bitovém režimu x86–64 má stejná služba jádra číslo 60 a jádro se zavolá instrukcí SYSCALL. Program tedy bude pochopitelně odlišný:
module assembly;
import std::io;
fn void main()
{
io::printn("About to exit");
asm("movq $$60, %rax");
asm("movq $$42, %rdi");
asm("syscall");
io::printn("I'm still alive...");
}
Povšimněte si společných rysů obou programů. Používá se zde AT&T syntaxe, v níž je napřed zapisován zdrojový operand a posléze operand cílový. Dále se před jména registrů zapisuje znak procent. A nejsložitější je zápis konstant, který vyžaduje použití dvojitých znaků dolaru – to vše proto, že se náš kód přímo posílá do LLVM.
Oba programy si můžeme snadno otestovat:
$ c3c compile-run asm_string_1.c3 Program linked to executable './assembly'. Launching ./assembly About to exit Program completed with exit code 42. $ echo $? 42
popř. druhý z programů:
$ c3c compile-run asm_string_2.c3 Program linked to executable './assembly'. Launching ./assembly About to exit Program completed with exit code 42. $ echo $? 42
8. Přístup k lokálním proměnným bez specifikace jejich jména
Pokud budeme chtít, aby se návratový kód procesu přečetl z lokální proměnné, je nutné postupovat dosti nízkoúrovňovým způsobem, protože k proměnným není možné v pseudofunkci asm přistupovat přes jejich jméno. Musíme vědět, jakým způsobem jsou lokální proměnné uloženy na zásobníkovém rámci a adresovat tyto proměnné přes bázový registr, což znamená nutnost změny kódu při každém přidání nebo ubrání lokální proměnné.
32bitová varianta programu může vypadat takto (povšimněte si adresy proměnné s offsetem –4):
module assembly;
import std::io;
fn void main()
{
uint return_value = 42;
io::printn("About to exit");
asm("movl $$1, %eax");
asm("movl -4(%rbp), %ebx");
asm("int $$0x80");
io::printn("I'm still alive...");
}
64bitová varianta může vypadat takto. Zde je ovšem typ proměnné odlišný a má tedy i jinou relativní adresu:
module assembly;
import std::io;
fn void main()
{
ulong return_value = 42;
io::printn("About to exit");
asm("movq $$60, %rax");
asm("movq -8(%rbp), %rdi");
asm("syscall");
io::printn("I'm still alive...");
}
9. Kontrola, jakým způsobem byl překlad proveden – přepínač –emit-asm
V případě, že se kombinuje zdrojový kód psaný v jazyce C3 s assemblerem a především ve chvíli, kdy se používá pseudofunkce asm, je vhodné se přesvědčit o tom, jak vlastně vypadá výsledek překladu do assembleru. LLVM totiž dokáže vygenerovat mezikód v assembleru, který je dokonce i poměrně dobře čitelný. Abychom tento kód získali, je nutné překladači jazyka C3 předat přepínač –emit-asm. Po překladu by měl kromě spustitelného souboru vzniknout i adresář asm s podadresářem linux-x64 (popř. jiným podle použitého operačního systému a architektury mikroprocesoru). V tomto adresáři budou všechny soubory v assembleru, které jsou součástí výsledné aplikace (tedy včetně knihovních funkcí):
$ ls -1 -sh total 5.3M 40K assembly.s 24K libc.s 156K std.atomic.s 172K std_collections_list.std.os.backtrace.Backtrace.s 16K std.core.ascii.s 172K std.core.builtin.s 240K std.core.dstring.s 544K std.core.mem.allocator.s 24K std.core.mem.s 24K std.core.string.conv.s 316K std.core.string.s 24K std.core.types.s 80K std.io.file.s 100K std.io.os.s 2.1M std.io.s 476K std.math.math_rt.s 32K std.math.s 88K std.os.backtrace.s 276K std.os.linux.s 256K std.os.posix.s 224K std.os.process.s
Nás bude primárně zajímat první soubor assembly.s (pojmenovaný podle balíčku), který mj. obsahuje i sekvenci instrukcí, které jsme zapsali přímo do asm:
.loc 1 9 5 is_stmt 1 # asm_string_3.c3:9:5
#APP
movq $60, %rax
#NO_APP
.loc 1 10 5 # asm_string_3.c3:10:5
#APP
movq %rdi, -8(%rbp)
#NO_APP
.loc 1 11 5 # asm_string_3.c3:11:5
#APP
syscall
#NO_APP
leaq .L.str.2(%rip), %rax
movq %rax, -136(%rbp)
movq $18, -128(%rbp)
10. Blok asm zabudovaný přímo do jazyka C3
Kromě pseudofunkce asm, která je poměrně špatně použitelná, podporuje programovací jazyk C3 i blok nazvaný též asm. Ten je již v praxi mnohem užitečnější, protože umožňuje v zápisu instrukcí používat jména lokálních a globálních proměnných, jejich adresy, parametry funkce atd. Tímto konceptem se tedy jazyk C3 přibližuje Turbo Pascalu a Borland C/C++, které nabízí podobné technologie.
V dalším textu se pokusíme v bloku asm realizovat volání služby jádra sloužícího k okamžitému ukončení procesu, pochopitelně se specifikací návratového kódu:
module assembly;
import std::io;
fn void main()
{
io::printn("About to exit");
asm
{
// kód pro ukončení procesu se specifikací
// návratového kódu procesu
}
io::printn("I'm still alive...");
}
11. Realizace volání služby operačního systému přes blok asm
Pro volání 32bitové služby jádra, která slouží k ukončení procesu, musíme nastavit pracovní registr EAX na hodnotu 1, do registru EBX vložit návratový kód a následně zavolat INT 80h. Tato sekvence tří instrukcí se v bloku asm realizuje velmi snadno, pouze nesmíme zapomenout vložit před jména registrů znak dolaru:
module assembly;
import std::io;
fn void main()
{
io::printn("About to exit");
asm
{
movl $eax, 1;
xorl $ebx, $ebx;
int 0x80;
}
io::printn("I'm still alive...");
}
Blok asm je převeden překladačem jazyka C3 do následující podoby:
#APP
movl $1, %eax
xorl %ebx, %ebx
int $128
#NO_APP
Samozřejmě nám nic nebrání v tom, aby návratový kód nebyl roven nule (výsledek operace XOR), ale například hodnotě 42:
module assembly;
import std::io;
fn void main()
{
io::printn("About to exit");
asm
{
movl $eax, 1;
xorl $ebx, 42;
int 0x80;
}
io::printn("I'm still alive...");
}
Ještě se pro zajímavost podívejme na volání 64bitové služby jádra. Zde se liší jak použité registry (64bitové), tak i jejich jména a především se volá služba s jiným číslem:
module assembly;
import std::io;
fn void main()
{
io::printn("About to exit");
asm
{
movq $rax, 60;
movq $rdi, 42;
syscall;
}
io::printn("I'm still alive...");
}
Překlad do assembleru vypadá následovně:
#APP
movq $60, %rax
movq $42, %rdi
syscall
#NO_APP
12. Přístup k lokálním proměnným z bloku asm s využitím jejich jména
Velmi užitečné je, že je možné v bloku asm používat názvy lokálních i globálních proměnných popř. parametry funkcí a metod. Můžeme si to otestovat na příkladu, ve kterém budeme jak číslo služby jádra, tak i požadovanou návratovou hodnotu procesu ukládat do dvojice lokálních proměnných nazvaných sys_exit a return_value (vhodného typu!). Tyto proměnné potom přímo použijeme v assembleru:
module assembly;
import std::io;
fn void main()
{
long sys_exit = 60;
long return_value = 42;
io::printn("About to exit");
asm
{
movq $rax, sys_exit;
movq $rdi, return_value;
syscall;
}
io::printn("I'm still alive...");
}
A jak bude vypadat překlad do assembleru? C3 za nás provede veškerou adresaci proměnných (ty jsou uloženy na zásobníkovém rámci), takže výsledek bude vypadat následovně:
.loc 1 10 5 is_stmt 1 # asm_block_5.c3:10:5
movq -8(%rbp), %rdx
movq -16(%rbp), %rsi
#APP
movq %rdx, %rax
movq %rsi, %rdi
syscall
#NO_APP
leaq .L.str.2(%rip), %rax
movq %rax, -152(%rbp)
movq $18, -144(%rbp)
13. Jakým způsobem se předávají parametry do funkcí a jak se z nich vrací hodnoty?
Náš kód zapsaný v assembleru byl prozatím velmi jednoduchý, protože byl přímo zařazen do nějaké (delší) funkce. Ovšem často se setkáme s požadavkem, aby byla v assembleru naprogramována celá funkce, tj. včetně části určené pro čtení předaných parametrů i části, která se stará o návratové hodnoty. Nejdříve si ověříme, jakým způsobem se vlastně do funkcí (naprogramovaných přímo v C3) předávají parametry a jak se popř. konstruuje zásobníkový rámec a vytváří (vrací) návratové hodnoty.
Do programu zapsaného v C3 zařadíme funkci add a posléze prozkoumáme její kód v assembleru:
module assembly;
import std::io;
fn int add(int x, int y)
{
return x+y;
}
fn void main()
{
io::printfn("1+2=%d", add(1, 2));
}
Výsledek překladu do assembleru se zákazem optimalizací dopadne následovně:
.file "assembly"
.text
.globl assembly.add # -- Begin function assembly.add
.p2align 4
.type assembly.add,@function
assembly.add: # @assembly.add
.Lfunc_begin0:
.file 1 "/tmp/ramdisk/c3c/build" "asm_block_6.c3"
.loc 1 4 0 # asm_block_6.c3:4:0
.cfi_startproc
# %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
.Ltmp0:
.loc 1 6 12 prologue_end # asm_block_6.c3:6:12
movl -4(%rbp), %eax
addl -8(%rbp), %eax
.loc 1 6 12 epilogue_begin is_stmt 0 # asm_block_6.c3:6:12
popq %rbp
.cfi_def_cfa %rsp, 8
retq
.Ltmp1:
.Lfunc_end0:
.size assembly.add, .Lfunc_end0-assembly.add
.cfi_endproc
Můžeme zde vidět uložení parametrů (EDI a ESI) na zásobníkový rámec:
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
Samotný zásobníkový rámec je vytvořen popř. odstraněn těmito instrukcemi (což je standardní způsob známý snad již půl století):
pushq %rbp
movq %rsp, %rbp
...
...
...
popq %rbp
retq
Celý kód funkce add je poměrně komplikovaný. Výhodnější bude provést překlad s povolením optimalizací:
.file "assembly"
.text
.globl assembly.add # -- Begin function assembly.add
.p2align 4
.type assembly.add,@function
assembly.add: # @assembly.add
.cfi_startproc
# %bb.0:
# kill: def $esi killed $esi def $rsi
# kill: def $edi killed $edi def $rdi
leal (%rdi,%rsi), %eax
retq
.Lfunc_end0:
.size assembly.add, .Lfunc_end0-assembly.add
Zde se žádný zásobníkový rámec netvoří a v těle funkce se pouze provede výpočet EAX=RDI+RSI. To tedy znamená, že již víme, jak se vrací návratová hodnota – právě přes pracovní registr EAX.
14. Implementace celé funkce ve vestavěném assembleru?
Otázkou nyní je, jestli a jak je možné ve vestavěném assembleru (tedy v bloku asm) implementovat celou plnohodnotnou funkci. V současné verzi jazyka C3 to pravděpodobně možné není, protože je nutné nějakým způsobem zajistit vracení hodnoty do volajícího kódu. Tj. sice můžeme v assembleru vytvořit prakticky celou funkci (včetně zpracování jejich parametrů), ovšem kromě závěrečného příkazu return. Výsledek by mohl vypadat takto:
module assembly;
import std::io;
fn int add(int x, int y)
{
int z;
asm {
movl $eax, x;
addl $eax, y;
movl z, $eax;
}
return z;
}
fn void main()
{
io::printfn("1+2=%d", add(1, 2));
}
Podívejme se na výsledek překladu, přičemž se omezíme pouze na funkci add:
.file "assembly"
.text
.globl assembly.add # -- Begin function assembly.add
.p2align 4
.type assembly.add,@function
assembly.add: # @assembly.add
.cfi_startproc
# %bb.0:
pushq %rax
.cfi_def_cfa_offset 16
#APP
movl %edi, %eax
addl %esi, %eax
movl %eax, %ecx
#NO_APP
movl %ecx, %eax
popq %rcx
.cfi_def_cfa_offset 8
retq
.Lfunc_end0:
.size assembly.add, .Lfunc_end0-assembly.add
.cfi_endproc
Povšimněte si, že se s výsledkem zbytečně provádí dvě instrukce – jednu jsme zapsali sami, druhá je výsledkem překladu příkazu return:
movl %eax, %ecx
#NO_APP
movl %ecx, %eax
15. Volání funkcí naprogramovaných v „externím“ assembleru
V mnoha případech se setkáme s požadavkem na zařazení již dříve vytvořených funkcí naprogramovaných v assembleru do programu, který je vytvořen v jazyku C3. Tento požadavek je možné relativně snadno splnit:
- Funkce naprogramovaná v assembleru je přeložena (assemblerem) do objektového kódu. Ten má na Linuxu koncovku .o, ve Windows pak .obj.
- Tento objektový kód je zařazen do sdílené knihovny (shared object, DLL). Můžeme použít například příkaz ar apod.
- Externí nativní funkce lze z jazyka C3 volat naprosto stejným způsobem, jako funkce definované přímo v jazyku C3.
Nevýhodou je, že funkce naprogramované v assembleru jsou prakticky zcela izolovány od zbytku aplikace, takže nemají přímý přístup k proměnným ani k jiným funkcím. Taktéž se musíme sami postarat o to, že kód funkcí v assemleru je nezávislý na umístění (PIC – Position-independent Code).
16. Krátké zopakování: volání externích funkcí naprogramovaných v jazyku C
Zopakujme si v krátkosti postup, který nám umožní zavolat nativní céčkovou funkci, která bude přeložena do staticky nebo dynamicky linkované knihovny. Definice takové funkce (v céčku) je triviální:
extern int add(int x, int y)
{
return x+y;
}
V dalším kroku provedeme překlad této funkce do objektového kódu s vložením tohoto kódu do sdílené knihovny nazvané libadder.so (samozřejmě lze použít libovolné jméno):
$ gcc -Wall -ansi -c -fPIC adder.c -o adder.o $ gcc -shared -Wl,-soname,libadder.so -o libadder.so adder.o
Volání takové funkce z jazyka C3 je snadné. Nesmíme jen zapomenout na to, že hlavička volané funkce musí být v C3 taktéž označena jako extern:
module assembly;
import std::io;
extern fn int add(int a, int b);
fn void main()
{
io::printf("%d\n", add(1, 2));
}
Při překladu tohoto příkladu je nutné překladači předat i jméno sdílené knihovny:
$ c3c compile -l libadder.so 40_call_c_function.c3 Program linked to executable './functions'.
Před spuštěním takto přeloženého programu si nastavte cestu ke sdíleným knihovnám:
$ export LD_LIBRARY_PATH=.
V přeloženém programu vypadá volání nativní funkce add takto (opět ověřeno s využitím přepínače –emit-asm):
movl $1, %edi
movl $2, %esi
.Ltmp0:
.loc 1 8 24 prologue_end # call_add_1.c3:8:24
callq add@PLT
Pro zajímavost se můžeme podívat, jak vlastně vypadá tělo céčkovské funkce po překladu. K tomuto účelu lze využít různé nástroje (včetně debuggeru), ovšem nejjednodušší bude využití standardního nástroje objdump:
$ objdump -d adder.o
Výsledkem bude výpis disassemblovaného kódu, který bude vypsán s použitím AT&T syntaxe:
adder.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <add>: 0: 8d 04 37 lea (%rdi,%rsi,1),%eax 3: c3 ret
Čitelnější je však (alespoň pro autora článku) syntaxe firmy Intel:
$ objdump -d adder.o -M intel adder.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <add> 0: 8d 04 37 lea eax,[rdi+rsi*1] 3: c3 ret
17. Zavolání funkce naprogramované v assembleru z jazyka C3
Nyní, když již víme, jakým způsobem je možné z programovacího jazyka C3 volat nativní funkci, se můžeme pokusit takovou funkci naprogramovat přímo v assembleru. Nebude to nic složitého, protože musíme pouze vědět, jak se předávají parametry do volané funkce a jak je vyřešeno zpětné předání návratové hodnoty volanému kódu. V ABI Linuxu se první dva (32bitové) celočíselné parametry předávají v registrech EDI a ESI (tedy nikoli přes zásobník) a návratová hodnota je uložena do pracovního registru EAX. To znamená, že můžeme provést snadný výpočet (není příliš optimalizovaný, ale je dobře čitelný):
mov eax, edi
add eax, esi
ret
Pro implementaci této jednoduché funkce využijeme assembler NASM, který nabízí čitelnou syntaxi. Celý zdrojový kód může vypadat následovně. Povšimněte si především toho, že je funkce označena jako global. Na to se nesmí zapomenout, protože jinak by nebylo jméno funkce korektně exportováno:
BITS 64
global add
section .text
add:
mov eax, edi
add eax, esi
ret
Nejprve provedeme překlad assemblerem do objektového souboru adder.o. Musíme přitom explicitně určit formát výsledného souboru přepínačem -f elf64:
$ nasm -f elf64 adder.asm
Podívejme se, jak vypadá obsah výsledného objektového souboru:
$ objdump -d adder.o
Výstup ve formátu AT&T:
adder.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <add>: 0: 89 f8 mov %edi,%eax 2: 01 f0 add %esi,%eax 4: c3 ret
Alternativně si samozřejmě můžeme vyžádat výstup ve formátu společnosti Intel:
$ objdump -d adder.o -M intel
Výsledek:
adder.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <add>: 0: 89 f8 mov eax,edi 2: 01 f0 add eax,esi 4: c3 ret
Z objektového souboru lze sdílenou knihovnu vytvořit různými způsoby, například přímo gcc:
$ gcc -shared -Wl,-soname,libadder.so -o libadder.so adder.o
Funkce add uložená do sdílené knihovny se zavolá naprosto stejným způsobem, jako tomu bylo u nativní funkce stejného jména naprogramované v jazyku C:
module assembly;
import std::io;
extern fn int add(int a, int b);
fn void main()
{
io::printf("%d\n", add(1, 2));
}
18. Shrnutí
Programovací jazyk C3 nabízí vývojářům tři základní technologie umožňující kombinaci zdrojového kódu zapsaného přímo v C3 s kódem psaným v assembleru. Nejjednodušší a v mnoha případech plně dostačující je využití bloku asm, který umožňuje přímo v assembleru přistupovat ke globálním i lokálním proměnným či k jejich adresám, což je v praxi výhodné. Nevýhodou (alespoň prozatím) je nemožnost zapisovat tímto způsobem „čistě assemblerovské“ funkce. Ovšem díky tomu, že C3 plně podporuje céčkovské ABI, můžeme takové funkce naprogramovat přímo v assembleru (my jsme použili NASM, ale samozřejmě je možné využít YASM nebo GNU Assembler) a využít volání této funkce z C3. Nevýhodou je oddělený překlad a taktéž nutnost mít hlavičku funkce uloženou ve dvou zdrojových kódech. A poslední možnost představuje pseudofunkce asm, která je však dosti nízkoúrovňová a navíc samotný jazyk C3 nemá žádnou kontrolu nad předávanými/generovanými instrukcemi.
19. Repositář s demonstračními příklady
Demonstrační příklady vytvořené pro nejnovější verzi programovacího jazyka C3 byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/c3-examples. Následují odkazy na jednotlivé příklady (či jejich nedokončené části).
Demonstrační příklady z prvního článku o programovacím jazyku C3:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 1 | factorial.c3 | realizace výpočtu faktoriálu | https://github.com/tisnik/c3-examples/blob/master/introduction/factorial.c3 |
| 2 | factorial_macro.c3 | výpočet faktoriálu konkrétní hodnoty implementovaný formou makra | https://github.com/tisnik/c3-examples/blob/master/introduction/factorial_macro.c3 |
| 3 | swap_macro.c3 | makro realizující prohození dvou hodnot | https://github.com/tisnik/c3-examples/blob/master/introduction/swap_macro.c3 |
| 4 | renderer.c | výpočet a vykreslení Juliovy množiny implementovaný v jazyku C | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer.c |
| 5 | renderer_v1.c3 | definice datové struktury s rozměry rastrového obrázku a skeleton všech funkcí | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v1.c3 |
| 6 | renderer_v2.c3 | anotace parametrů funkcí typu ukazatel (pointer) | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v2.c3 |
| 7 | renderer_v3.c3 | statická kontrola, zda se nepředávají neinicializované ukazatele | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v3.c3 |
| 8 | renderer_v4.c3 | runtime kontrola, zda se nepředávají neinicializované ukazatele | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v4.c3 |
| 9 | renderer_v5.c3 | první (nekorektní) varianta funkce pro inicializaci barvové palety | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v5.c3 |
| 10 | renderer_v6.c3 | druhá (korektní) varianta funkce pro inicializaci barvové palety | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v6.c3 |
| 11 | renderer_v7.c3 | volání knihovní I/O funkce a volání nativní céčkovské funkce | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v7.c3 |
| 12 | renderer_v8.c3 | plně funkční program pro výpočet a vykreslení Juliovy množiny | https://github.com/tisnik/c3-examples/blob/master/introduction/renderer_v8.c3 |
Demonstrační příklady ze druhého článku o jazyku C3:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 13 | 01_just_main.c3 | struktura nejjednoduššího programu obsahujícího pouze prázdnou funkci main | https://github.com/tisnik/c3-examples/blob/master/c3-basics/01_just_main.c3 |
| 14 | 02_module_name.c3 | struktura programu s uvedeným plným jménem modulu | https://github.com/tisnik/c3-examples/blob/master/c3-basics/02_module_name.c3 |
| 15 | 03_hello_world.c3 | klasický program typu „Hello, world!“ napsaný v jazyku C3 | https://github.com/tisnik/c3-examples/blob/master/c3-basics/03_hello_world.c3 |
| 16 | 04_exit_value.c3 | ukončení procesu s předáním návratového kódu zpět volajícímu programu | https://github.com/tisnik/c3-examples/blob/master/c3-basics/04_exit_value.c3 |
| 17 | 05_c_function.c3 | zavolání funkce definované v knihovně programovacího jazyka C | https://github.com/tisnik/c3-examples/blob/master/c3-basics/05_c_function.c3 |
| 18 | 06_bool_type.c3 | definice proměnných typu pravdivostní hodnota (bool) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/06_bool_type.c3 |
| 19 | 07_int_to_bool.c3 | implicitní převod hodnoty typu int na pravdivostní hodnotu (nekorektní řešení) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/07_int_to_bool.c3 |
| 20 | 08_int_to_bool.c3 | explicitní převod hodnoty typu int na pravdivostní hodnotu (korektní řešení) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/08_int_to_bool.c3 |
| 21 | 09_int_to_bool.c3 | explicitní převod hodnoty typu int na pravdivostní hodnotu (nekorektní řešení) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/09_int_to_bool.c3 |
| 22 | 10_bool_sizeof.c3 | zjištění velikosti paměti obsazené hodnotou typu bool | https://github.com/tisnik/c3-examples/blob/master/c3-basics/10_bool_sizeof.c3 |
| 23 | 11_int_types.c3 | definice proměnných typu celé číslo se znaménkem s různou bitovou šířkou | https://github.com/tisnik/c3-examples/blob/master/c3-basics/11_int_types.c3 |
| 24 | 12_uint_types.c3 | definice proměnných typu celé číslo bez znaménka s různou bitovou šířkou | https://github.com/tisnik/c3-examples/blob/master/c3-basics/12_uint_types.c3 |
| 25 | 13_no_suffixes.c3 | celočíselné konstanty bez uvedení suffixu (bitové šířky) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/13_no_suffixes.c3 |
| 26 | 14_suffixes.c3 | celočíselné konstanty s uvedením sufficu (bitové šířky) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/14_suffixes.c3 |
| 27 | 15_int_sizeof.c3 | zjištění velikosti paměti obsazené celočíselnými hodnotami se znaménkem | https://github.com/tisnik/c3-examples/blob/master/c3-basics/15_int_sizeof.c3 |
| 28 | 16_uint_sizeof.c3 | zjištění velikosti paměti obsazené celočíselnými hodnotami bez znaménka | https://github.com/tisnik/c3-examples/blob/master/c3-basics/16_uint_sizeof.c3 |
| 29 | 17_int_conversions.c3 | korektní převody mezi celočíselnými hodnotami s různou bitovou šířkou | https://github.com/tisnik/c3-examples/blob/master/c3-basics/17_int_conversions.c3 |
| 30 | 18_int_conversions.c3 | nekorektní převody mezi celočíselnými hodnotami s různou bitovou šířkou | https://github.com/tisnik/c3-examples/blob/master/c3-basics/18_int_conversions.c3 |
| 31 | 19_int_conversions.c3 | explicitní převody a přetečení hodnot | https://github.com/tisnik/c3-examples/blob/master/c3-basics/19_int_conversions.c3 |
| 32 | 20_float_types.c3 | definice proměnných typu numerická hodnota s plovoucí řádovou čárkou (tečkou) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/20_float_types.c3 |
| 33 | 21_vector_type.c3 | definice vektoru obsahujícího celočíselné hodnoty | https://github.com/tisnik/c3-examples/blob/master/c3-basics/21_vector_type.c3 |
| 34 | 22_vector_operations.c3 | základní operace s celými vektory | https://github.com/tisnik/c3-examples/blob/master/c3-basics/22_vector_operations.c3 |
| 35 | 23_vector_sizes.c3 | zjištění a tisk velikosti vektorů (různé datové typy prvků vektorů, shodná délka) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/23_vector_sizes.c3 |
| 36 | 24_vector_sizes.c3 | zjištění a tisk velikosti vektorů (stejné datové typy prvků vektorů, odlišná délka) | https://github.com/tisnik/c3-examples/blob/master/c3-basics/24_vector_sizes.c3 |
Demonstrační příklady použité ve třetím článku o jazyku C3:
Demonstrační příklady ze čtvrtého o jazyku C3:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 57 | 01_program_stub.c3 | struktura programu s uvedeným plným jménem modulu | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/01_program_stub.c3 |
| 58 | 02_if.c3 | nejjednodušší forma rozvětvení založené na konstrukci if | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/02_if.c3 |
| 59 | 03_if_else.c3 | plné rozvětvení realizované konstrukcí if-else | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/03_if_else.c3 |
| 60 | 04_improper_if.c3 | nekorektní způsob zápisu programové konstrukce if-else (porovnání s jazykem C) | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/04_improper_if.c3 |
| 61 | 05_improper_if.c3 | nekorektní způsob zápisu programové konstrukce if-else (porovnání s jazykem C) | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/05_improper_if.c3 |
| 62 | 06_if_else_if.c3 | složitější rozvětvení založené na programové konstrukci if-else if-else | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/06_if_else_if.c3 |
| 63 | 07_switch_basic.c3 | základní forma vícenásobného rozvětvení založeného na konstrukci switch-case | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/07_switch_basic.c3 |
| 64 | 08_switch_basic.c3 | větší množství podmínek a programová konstrukce switch-case | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/08_switch_basic.c3 |
| 65 | 09_switch_condition.c3 | podmínky zapsané ve větvích programové konstrukci switch-case vyhodnocované v čase běhu procesu | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/09_switch_condition.c3 |
| 66 | 10_switch_true.c3 | konstrukce switch-case bez uvedeného výrazu za klíčovým slovem switch | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/10_switch_true.c3 |
| 67 | 11_switch_break.c3 | zápis prázdné větve default v programové konstrukci switch-case | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/11_switch_break.c3 |
| 68 | 12_switch_nextcase.c3 | pokračování ve vykonávání konstrukce switch-case vynucené klíčovým slovem nextcase | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/12_switch_nextcase.c3 |
| 69 | 13_for_loop.c3 | základní forma programové smyčky realizované klíčovým slovem for | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/13_for_loop.c3 |
| 70 | 14_foreach_loop.c3 | základní forma programové smyčky typu for-each | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/14_foreach_loop.c3 |
| 71 | 15_foreach_loop.c3 | programová smyčka for-each vracející index prvku i hodnotu prvku | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/15_foreach_loop.c3 |
| 72 | 16_foreach_loop.c3 | modifikace obsahu pole v programové smyčce for-each | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/16_foreach_loop.c3 |
| 73 | 17_foreach_loop.c3 | pokus o modifikaci obsahu procházeného pole | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/17_foreach_loop.c3 |
| 74 | 18_foreach_loop.c3 | modifikace procházeného pole přes ukazatel na prvek | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/18_foreach_loop.c3 |
| 75 | 19_foreach_r_loop.c3 | programová smyčka for-each, ve které se sekvencí prochází v opačném pořadí | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/19_foreach_r_loop.c3 |
| 76 | 20_while_loop.c3 | základní forma programové smyčky typu while | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/20_while_loop.c3 |
| 77 | 21_while_loop2.c3 | programová smyčka typu while s konstrukcí break | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/21_while_loop2.c3 |
| 78 | 22_nested_loops.c3 | realizace vnořených programových smyček | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/22_nested_loops.c3 |
| 79 | 23_break.c3 | vnořené programové smyčky a příkaz break: ukončení vnitřní smyčky | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/23_break.c3 |
| 80 | 24_break.c3 | vnořené programové smyčky a příkaz break: ukončení vnější smyčky | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/24_break.c3 |
| 81 | 25_break.c3 | vnořené programové smyčky a příkaz break, varianta se smyčkami typu while | https://github.com/tisnik/c3-examples/blob/master/c3-control-flow/25_break.c3 |
Demonstrační příklady z pátého článku o jazyku C3:
Následují odkazy na demonstrační příklady z článku předchozího:
| # | Příklad | Stručný popis | Adresa | |
|---|---|---|---|---|
| 100 | 01_regular_function.c3 | deklarace běžných funkcí v jazyku C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/01_regular_function.c3 | |
| 101 | 02_check_arguments.c3 | kontrola parametrů předávaných do funkce překladačem jazyka C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/02_check_arguments.c3 | |
| 102 | 03_default_arguments.c3 | funkce s jedním parametrem, který má nastavenou výchozí hodnotu a jedním běžným parametrem (korektní pořadí parametrů) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/03_default_arguments.c3 | |
| 103 | 04_default_arguments.c3 | funkce se všemi parametry s nastavenu výchozí hodnotou | https://github.com/tisnik/c3-examples/blob/master/c3-functions/04_default_arguments.c3 | |
| 104 | 05_default_arguments.c3 | funkce s jedním parametrem, který má nastavenou výchozí hodnotu a jedním běžným parametrem (nekorektní pořadí parametrů) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/05_default_arguments.c3 | |
| 105 | 06_named_arguments.c3 | explicitní uvedení jmen parametrů při volání funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/06_named_arguments.c3 | |
| 106 | 07_named_arguments.c3 | explicitní uvedení jmen parametrů při volání funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/07_named_arguments.c3 | |
| 107 | 08_named_default_arguments.c3 | pojmenování parametrů s výchozí hodnotou při volání funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/08_named_default_arguments.c3 | |
| 108 | 09_sum.c3 | realizace funkce pro výpočet součtu všech předaných hodnot | https://github.com/tisnik/c3-examples/blob/master/c3-functions/09_sum.c3 | |
| 109 | 10_sum.c3 | předání obsahu pole do funkce s proměnným počtem parametrů | https://github.com/tisnik/c3-examples/blob/master/c3-functions/10_sum.c3 | |
| 110 | 11_varargs.c3 | pořadí předávání parametrů do funkce s proměnným počtem parametrů (nekorektní způsob použití) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/11_varargs.c3 | |
| 111 | 12_varargs.c3 | pořadí předávání parametrů do funkce s proměnným počtem parametrů (korektní způsob použití) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/12_varargs.c3 | |
| 112 | 13_optional.c3 | funkce vracející hodnotu typu Optional | https://github.com/tisnik/c3-examples/blob/master/c3-functions/13_optional.c3 | |
| 113 | 14_optional.c3 | využití operátoru ?? | https://github.com/tisnik/c3-examples/blob/master/c3-functions/14_optional.c3 | |
| 114 | 15_contract.c3 | kontrakty uvedené u funkcí | https://github.com/tisnik/c3-examples/blob/master/c3-functions/15_contract.c3 | |
| 115 | 16_contract.c3 | kontrakty uvedené u funkcí | https://github.com/tisnik/c3-examples/blob/master/c3-functions/16_contract.c3 | |
| 116 | 17_c_declaration.c3 | deklarace funkce bez parametrů „céčkovým způsobem“ (nekorektní zápis) | https://github.com/tisnik/c3-examples/blob/master/c3-functions/17_c_declaration.c3 | |
| 117 | 18_check_return_type.c3 | kontrola návratové hodnoty překladačem jazyka C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/18_check_return_type.c3 | |
| 118 | 19_check_return_value.c3 | kontrola počtu návratových hodnot překladačem jazyka C3 | https://github.com/tisnik/c3-examples/blob/master/c3-functions/19_check_return_value.c3 | |
| 119 | 20_in_out_params.c3 | předání ukazatelů do volané funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/20_in_out_params.c3 | |
| 120 | 21_in_out_params.c3 | označení ukazatelů kontrakty [in] a [out] | https://github.com/tisnik/c3-examples/blob/master/c3-functions/21_in_out_params.c3 | |
| 121 | 22_in_out_params.c3 | označení ukazatelů kontrakty [in] a [out] | https://github.com/tisnik/c3-examples/blob/master/c3-functions/22_in_out_params.c3 | |
| 122 | 23_void_pointer.c3 | předávání ukazatelů typu void * do volané funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/23_void_pointer.c3 | |
| 123 | 24_contract.c3 | kontrakt zapsaný před hlavičkou funkce | https://github.com/tisnik/c3-examples/blob/master/c3-functions/24_contract.c4 |
Demonstrační příklady z předchozího článku o funkcích v C3:
Příklady z předchozího článku s popisem makrosystému jazyka C3:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 145 | 01_add_macro.c3 | jednoduché makro, které může být expandováno v rámci výrazu | https://github.com/tisnik/c3-examples/blob/master/c3-macros/01_add_macro.c3 |
| 146 | 02_add_macro.c3 | makro je generické, lze použít s hodnotami různých typů | https://github.com/tisnik/c3-examples/blob/master/c3-macros/02_add_macro.c3 |
| 147 | 03_macro_expansion1.c | expanze makro provedená preprocesorem jazyka C (parametry nejsou uzávorkovány) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/03_macro_expansion1.c |
| 148 | 03_macro_expansion2.c | expanze makro provedená preprocesorem jazyka C (parametry jsou uzávorkovány) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/03_macro_expansion2.c |
| 149 | 03_macro_expansion.c3 | řešení v jazyku C3 bez nutnosti uzávorkování parametrů makra | https://github.com/tisnik/c3-examples/blob/master/c3-macros/03_macro_expansion.c3 |
| 150 | 04_macro_expansion1.c | expanze makro provedená preprocesorem jazyka C (tělo makra není v závorkách) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/04_macro_expansion1.c |
| 151 | 04_macro_expansion2.c | expanze makro provedená preprocesorem jazyka C (tělo makra je v závorkách) | https://github.com/tisnik/c3-examples/blob/master/c3-macros/04_macro_expansion2.c |
| 152 | 04_macro_expansion.c3 | řešení v jazyku C3 bez nutnosti zápisu těla makra do závorek | https://github.com/tisnik/c3-examples/blob/master/c3-macros/04_macro_expansion.c3 |
| 153 | 05_typed_macro.c3 | makro s definicí typů parametrů | https://github.com/tisnik/c3-examples/blob/master/c3-macros/05_typed_macro.c3 |
| 154 | 06_typed_macro.c3 | makro s definicí typů parametrů: příklad jeho expanze | https://github.com/tisnik/c3-examples/blob/master/c3-macros/06_typed_macro.c3 |
| 155 | 07_compile_time1.c3 | expanze makra v čase překladu: nefunkční varianta s konstantami | https://github.com/tisnik/c3-examples/blob/master/c3-macros/07_compile_time1.c3 |
| 156 | 08_compile_time2.c3 | expanze makra v čase překladu: funkční varianta s konstantami | https://github.com/tisnik/c3-examples/blob/master/c3-macros/08_compile_time2.c3 |
| 157 | 09_compile_time3.c3 | expanze makra v čase překladu: nefunkční varianta s proměnnými | https://github.com/tisnik/c3-examples/blob/master/c3-macros/09_compile_time3.c3 |
| 158 | 10_swap_macro1.c3 | realizace makra pro prohození obsahu dvou proměnných: nefunkční varianta | https://github.com/tisnik/c3-examples/blob/master/c3-macros/10_swap_macro1.c3 |
| 159 | 11_swap_macro2.c3 | realizace makra pro prohození obsahu dvou proměnných: funkční varianta | https://github.com/tisnik/c3-examples/blob/master/c3-macros/11_swap_macro2.c3 |
| 160 | 12_varargs.c3 | makro s proměnným počtem argumentů | https://github.com/tisnik/c3-examples/blob/master/c3-macros/12_varargs.c3 |
Demonstrační příklady uvedené v článku o nedefinovaném chování v jazyce C3:
Demonstrační příklady uvedené v článku o přetěžování operátorů:
Demonstrační příklady z dnešního článku o podpoře assembleru v jazyku C3:
| # | Příklad | Stručný popis | Adresa |
|---|---|---|---|
| 197 | asm_string1.c3 | volání 32bitové funkce jádra operačního systému pro ukončení procesu | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_string1.c3 |
| 198 | asm_string2.c3 | volání 64bitové funkce jádra operačního systému pro ukončení procesu | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_string2.c3 |
| 199 | asm_string3.c3 | přístup k lokální 32bitové proměnné přes její adresu na zásobníkovém rámci | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_string3.c3 |
| 200 | asm_string4.c3 | přístup k lokální 64bitové proměnné přes její adresu na zásobníkovém rámci | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_string4.c3 |
| 201 | asm_block1.c3 | příprava pro volání kódu zapsaného v assembleru | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_block1.c3 |
| 202 | asm_block2.c3 | volání 32bitové funkce jádra operačního systému pro ukončení procesu | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_block2.c3 |
| 203 | asm_block3.c3 | podobné předchozímu příkladu, ovšem používá se jedna odlišná instrukce | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_block3.c3 |
| 204 | asm_block4.c3 | volání 64bitové funkce jádra operačního systému pro ukončení procesu | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_block4.c3 |
| 205 | asm_block5.c3 | přístup k lokálním 64bitovým proměnným přes její adresu na zásobníkovém rámci | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_block5.c3 |
| 206 | asm_add1.c3 | realizace funkce pro součet dvou hodnot v jazyku C3 | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_add1.c3 |
| 207 | asm_add2.c3 | realizace funkce pro součet dvou hodnot v kombinaci assembler+C3 | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/asm_add2.c3 |
| 208 | adder.c | funkce pro součet dvou celých čísel naprogramovaná v jazyku C | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/adder.c |
| 209 | adder.asm | funkce pro součet dvou celých čísel naprogramovaná v assembleru | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/adder.asm |
| 210 | call_add.c3 | volání nativní funkce (může být naprogramovaná jak v C, tak i v assembleru) | https://github.com/tisnik/c3-examples/blob/master/c3-assembly/call_add.c3 |
20. Odkazy na Internetu
- Programovací jazyk C3: evoluce, nikoli revoluce
https://www.root.cz/clanky/programovaci-jazyk-c3-evoluce-nikoli-revoluce/ - Programovací jazyk C3: datové typy pro moderní architektury
https://www.root.cz/clanky/programovaci-jazyk-c3-datove-typy-pro-moderni-architektury/ - Programovací jazyk C3: složené datové typy a kontejnery
https://www.root.cz/clanky/programovaci-jazyk-c3-slozene-datove-typy-a-kontejnery/ - The C3 Programming Language
https://c3-lang.org/ - C3 For C Programmers
https://c3-lang.org/language-overview/primer/ - C3 is a C-like language trying to be an incremental improvement over C rather than a whole new language
https://www.reddit.com/r/ProgrammingLanguages/comments/oohij6/c3_is_a_clike_language_trying_to_be_an/ - Tiobe index
https://www.tiobe.com/tiobe-index/ - PYPL PopularitY of Programming Language
https://pypl.github.io/PYPL.html - C3 Tutorial
https://learn-c3.org/ - History of programming languages
https://devskiller.com/history-of-programming-languages/ - History of programming languages (Wikipedia)
https://en.wikipedia.org/wiki/History_of_programming_languages - D language
https://dlang.org/ - Zig programming language
https://ziglang.org/ - V language
https://vlang.io/ - D programming language
https://en.wikipedia.org/wiki/D_(programming_language) - Zig programming language (Wikipedia)
https://en.wikipedia.org/wiki/Zig_(programming_language) - V programming language (Wikipedia)
https://en.wikipedia.org/wiki/V_(programming_language) - Syntax highlighting for C3's programming language
https://github.com/Airbus5717/c3.vim - Go factorial
https://gist.github.com/esimov/9622710 - Generational list of programming languages
https://en.wikipedia.org/wiki/Generational_list_of_programming_languages - The Language Tree: Almost Every Programming Language Ever Made
https://github.com/Phileosopher/langmap - List of C-family programming languages
https://en.wikipedia.org/wiki/List_of_C-family_programming_languages - Compatibility of C and C++
https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B - C++23: compatibility with C
https://www.sandordargo.com/blog/2023/08/23/cpp23-c-compatibility - Can C++ Run C Code? Understanding Language Compatibility
https://www.codewithc.com/can-c-run-c-code-understanding-language-compatibility/ - C3: Comparisons With Other Languages
https://c3-lang.org/faq/compare-languages/ - C3 Programming Language Gains Traction as Modern C Alternative
https://biggo.com/news/202504040125_C3_Programming_Language_Alternative_to_C - The case against a C alternative
https://c3.handmade.network/blog/p/8486-the_case_against_a_c_alternative - C (programming language) Alternatives
https://alternativeto.net/software/c-programming-language-/ - Seriál Programovací jazyk Go
https://www.root.cz/serialy/programovaci-jazyk-go/ - Is C3 the Underdog That Will Overtake Zig and Odin?
https://bitshifters.cc/2025/05/22/c3-c-tradition.html - „Hello, World!“ program
https://en.wikipedia.org/wiki/%22Hello%2C_World!%22_program - The C Programming Language
https://en.wikipedia.org/wiki/The_C_Programming_Language - Kontejner (abstraktní datový typ)
https://cs.wikipedia.org/wiki/Kontejner_(abstraktn%C3%AD_datov%C3%BD_typ) - Are arrays not considered containers because they are not based off of a class?
https://stackoverflow.com/questions/37710975/are-arrays-not-considered-containers-because-they-are-not-based-off-of-a-class - Array declaration (C, C++)
https://en.cppreference.com/w/cpp/language/array.html - Understanding the Apple ‘goto fail;’ vulnerability
https://www.blackduck.com/blog/understanding-apple-goto-fail-vulnerability-2.html - Branch (computer science)
https://en.wikipedia.org/wiki/Branch_(computer_science) - Conditional (computer programming)
https://en.wikipedia.org/wiki/Conditional_(computer_programming) - Dangling else
https://en.wikipedia.org/wiki/Dangling_else - Switch statement
https://en.wikipedia.org/wiki/Switch_statement - Compiler correctness
https://en.wikipedia.org/wiki/Compiler_correctness - Anonymous function
https://en.wikipedia.org/wiki/Anonymous_function - Closure (computer programming)
https://en.wikipedia.org/wiki/Closure_(computer_programming) - How to implement closures in C
https://hokstad.com/how-to-implement-closures - An Overview of Macros in Rust
http://words.steveklabnik.com/an-overview-of-macros-in-rust - A Practical Intro to Macros in Rust 1.0
https://danielkeep.github.io/practical-intro-to-macros.html - The Rust Programming Language: macros
https://doc.rust-lang.org/beta/book/macros.html - Rust by example: 15 macro_rules!
http://rustbyexample.com/macros.html - Undefined behavior
https://en.wikipedia.org/wiki/Undefined_behavior - Undefined Behavior in C and C++
https://www.geeksforgeeks.org/cpp/undefined-behavior-c-cpp/ - The Rust reference: Behavior considered undefined
https://doc.rust-lang.org/reference/behavior-considered-undefined.html#behavior-considered-undefined - Why would a language have a concept of undefined behavior instead of raising an error?
https://langdev.stackexchange.com/questions/481/why-would-a-language-have-a-concept-of-undefined-behavior-instead-of-raising-an - Undefined behavior
https://riptutorial.com/c/topic/364/undefined-behavior - C3: Undefined Behaviour
https://c3-lang.org/language-rules/undefined-behaviour/ - Undefined behavior in C and C++
https://russellw.github.io/undefined-behavior - C3 goes game and maths friendly with operator overloading
https://c3.handmade.network/blog/p/9019-c3_goes_game_and_maths_friendly_with_operator_overloading - Are operator overloadings in C++ more trouble than they're worth?
https://stackoverflow.com/questions/707081/are-operator-overloadings-in-c-more-trouble-than-theyre-worth - Why is operator overloading sometimes considered a bad practice?
https://www.reddit.com/r/ProgrammingLanguages/comments/19cl30z/why_is_operator_overloading_sometimes_considered/ - I don't understand the arguments against operator overloading
https://softwareengineering.stackexchange.com/questions/25154/i-dont-understand-the-arguments-against-operator-overloading - BBC BASIC
http://www.bbcbasic.co.uk/bbcbasic.html - BBC BASIC
http://mdfs.net/Software/BBCBasic/ - BBC BASIC (Z80) for the ZX Spectrum
http://mdfs.net/Software/BBCBasic/Spectrum/ - BBC BASIC (Wikipedia CZ)
http://en.wikipedia.org/wiki/BBC_BASIC - History of Pascal
https://www.taoyue.com/tutorials/pascal/history.html - Antique Software: Turbo Pascal version 5.5
http://edn.embarcadero.com/article/20803 - Turbo Pascal Object-oriented programming guide
http://edn.embarcadero.com/article/images/20803/TP55_OOP_Guide.pdf - FreePascal
http://www.freepascal.org/ - FreePascal (dokumentace dostupná online)
https://www.freepascal.org/docs.html - Turbo Pascal (With DOSBox)
https://tpwdb.weebly.com/ - Modern Pascal
http://modernpascal.com/ - Free Pascal: Using assembler in the sources
https://www.freepascal.org/docs-html/prog/progse9.html#x143–1440003.1 - Is inline asm part of the ANSI C standard?
https://stackoverflow.com/questions/13870489/is-inline-asm-part-of-the-ansi-c-standard - Position-independent code
https://en.wikipedia.org/wiki/Position-independent_code






