Programovací jazyk C3: evoluce, nikoli revoluce

11. 9. 2025
Doba čtení: 37 minut

Sdílet

Programovací jazyk C3
Autor: Root.cz s využitím Zoner AI
Jazyk C vznikl před více než padesáti lety a stále se používá. Za tuto dobu vzniklo poměrně velké množství dalších jazyků, jejichž cílem bylo od původního C odvodit výkonnější, bezpečnější či jiným způsobem „lepší“ jazyk.

Obsah

1. Programovací jazyk C3: evoluce, nikoli revoluce

2. Instalace balíčků vyžadovaných pro překlad C3

3. Překlad C3

4. První spuštění překladače

5. Je jazyk C3 zpětně kompatibilní s jazykem C?

6. Porovnání syntaxe jazyka C s dalšími mainstreamovými programovacími jazyky

7. První reálný program vytvořený v jazyku C3

8. Makra v programovacím jazyku C3

9. Od C k C3

10. Definice datové struktury s rozměry rastrového obrázku

11. Skeleton všech funkcí

12. Anotace parametrů funkcí

13. Statická kontrola, zda se nepředávají neinicializované ukazatele

14. Kontrola neinicializovaných ukazatelů v čase běhu programu

15. Přepis funkce pro inicializaci palety a striktnější typová kontrola

16. Výpočty barev v barvové paletě s hodnotami typu char a nikoli int

17. Volání knihovní I/O funkce a volání nativní céčkovské I/O funkce

18. Funkční varianta céčkovského programu z deváté kapitoly

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Programovací jazyk C3: evoluce, nikoli revoluce

V perexu dnešního článku jsme si připomenuli, že jeden z nejpoužívanějších programovacích jazyků vůbec – C – vznikl před více než padesáti lety. A i když se C postupně vyvíjelo, nikdy nedošlo k nějaké dramatické a zpětně nekompatibilní změně. Jazyk C je tak (i přesto, že existuje několik oficiálních standardů) velmi konzervativní a stabilní. Na druhou stranu se vyvíjí samotná oblast IT, takže není divu, že vzniklo relativně velké množství jazyků, které jsou od jazyka C více či méně odvozeny (někdy zcela – viz první verze C++, jindy je zde patrná velká inspirace, jako je tomu u jazyků V, Zig a vlastně i u Go a Rustu). Relativně novým členem v rodině jazyků odvozených od céčka je jazyk nazvaný C3.

C3 se – i přes zdánlivou značnou podobnost – ve skutečnosti od klasického céčka v mnoha ohledech odlišuje. Většina nově přidaných vlastností zjednodušuje tvorbu programů, umožňuje psát bezpečnější programy (resp. zjednodušuje tvorbu bezpečnějších programů, protože C toto nechává zcela na programátorovi) a přidává do jazyka další užitečné vlastnosti. Jednotlivými vlastnostmi programovacího jazyka C3 se budeme podrobněji zabývat v samostatných článcích, takže si prozatím tyto vlastnosti alespoň ve stručnosti vypišme:

  1. Je zaručena kompatibilita s céčkovým ABI (velmi důležité)
  2. Namísto vkládání hlavičkových souborů se používá systém modulů
  3. Moduly mají vlastní jmenné prostory, takže nedochází ke konfliktům u stejně pojmenovaných identifikátorů
  4. Lze zapisovat podmínky, které mají být splněny před a po příkazu/funkci
  5. Lze definovat vlastnosti ukazatelů předávaných do funkcí (zda mohou být null atd.)
  6. Plná podpora pro UTF-8
  7. Možnost práce s řezy (podobně jako v Go, opět velmi užitečné)
  8. Nový systém maker; jde o skutečná makra, nikoli o textové substituce
  9. V jazyku je méně explicitně nedefinovaných chování (tím je C proslulé, a je k tomu dobrý důvod)

Jazyk C3 je na poli programovacích jazyků skutečně nováčkem. Ostatně prozatím ani nebyl přidán do seznamu jazyků více či méně odvozených od jazyka C (kam bezpochyby patří). Tento seznam lze nalézt na adrese https://en.wikipedia.org/wi­ki/List_of_C-family_programming_languages (najdeme zde „staré známé“, včetně C++, Rustu i Go).

Poznámka: osobně si nemyslím, že by C3 někdy céčko nahradil (stejně jako C23 pravděpodobně nenahradí starší verze), ale je zajímavé sledovat, jakým směrem se mohou programovací jazyky vyvíjet – namísto přidávání dalších a dalších nových vlastností se v případě C3 spíše jedná o jemné úpravy.

2. Instalace balíčků vyžadovaných pro překlad C3

V navazující kapitole si popíšeme způsob překladu všech základních nástrojů programovacího jazyka C3, protože tyto nástroje ještě nebývají součástí distribucí Linuxu (popř. jsou zastaralé) a není je tedy možné nainstalovat přímo. Ovšem pro samotný překlad a slinkování C3 je nutné, aby byly v systému nainstalovány další překladače (Clang, Git, cmake) a knihovny (LLVM-devel atd.). Ukažme si instalaci balíčků s těmito nástroji v systémech s RPM/DNF:

$ sudo dnf install cmake clang git llvm llvm-devel lld lld-devel ncurses-devel

Samotný průběh instalace pochopitelně do značné míry závisí na tom, které balíčky již byly nainstalovány (zde například cmake a git) a které nikoli:

Last metadata expiration check: 0:14:31 ago on Fri 29 Aug 2025 11:36:33 AM CEST.
Package cmake-3.30.8-1.fc41.x86_64 is already installed.
Package git-2.49.0-1.fc41.x86_64 is already installed.
Dependencies resolved.
========================================================================================================================================
 Package                            Architecture             Version                                    Repository                 Size
========================================================================================================================================
Installing:
 clang                              x86_64                   18.1.8-2.fc41                              updates                    78 k
 lld                                x86_64                   18.1.8-1.fc41                              updates                    36 k
 lld-devel                          x86_64                   18.1.8-1.fc41                              updates                    25 k
 llvm                               x86_64                   18.1.8-4.fc41                              updates                    27 M
 llvm-devel                         x86_64                   18.1.8-4.fc41                              updates                   4.1 M
 ncurses-devel                      x86_64                   6.4-12.20240127.fc41                       fedora                    562 k
Installing dependencies:
 libedit-devel                      x86_64                   3.1-54.20250104cvs.fc41                    updates                    41 k
 lld-libs                           x86_64                   18.1.8-1.fc41                              updates                   1.5 M
 llvm-googletest                    x86_64                   18.1.8-4.fc41                              updates                   382 k
 llvm-static                        x86_64                   18.1.8-4.fc41                              updates                    36 M
 llvm-test                          x86_64                   18.1.8-4.fc41                              updates                   648 k
 ncurses-c++-libs                   x86_64                   6.4-12.20240127.fc41                       fedora                     38 k
 
Transaction Summary
========================================================================================================================================
Install  12 Packages
 
Total download size: 71 M
Installed size: 409 M
Is this ok [y/N]:
 
Installed:
  clang-18.1.8-2.fc41.x86_64           libedit-devel-3.1-54.20250104cvs.fc41.x86_64      lld-18.1.8-1.fc41.x86_64
  lld-devel-18.1.8-1.fc41.x86_64       lld-libs-18.1.8-1.fc41.x86_64                     llvm-18.1.8-4.fc41.x86_64
  llvm-devel-18.1.8-4.fc41.x86_64      llvm-googletest-18.1.8-4.fc41.x86_64              llvm-static-18.1.8-4.fc41.x86_64
  llvm-test-18.1.8-4.fc41.x86_64       ncurses-c++-libs-6.4-12.20240127.fc41.x86_64      ncurses-devel-6.4-12.20240127.fc41.x86_64
 
Complete!

Po těchto krocích doinstalujeme ještě několik dalších knihoven (ve vývojové verzi), které mohou být využity moduly programovacího jazyka C3. Instalace těchto balíčků sice není nezbytně nutná, ovšem jejich neexistence omezí možnosti programů psaných v C3:

$ sudo dnf install libcurl-devel zlib-devel libzstd-devel libxml2-devel libffi-devel

Z výpisu je opět patrné, že některé z těchto balíčků již byly v systému nainstalovány:

Last metadata expiration check: 0:15:55 ago on Fri 29 Aug 2025 11:36:33 AM CEST.
Package zlib-ng-compat-devel-2.1.7-2.fc41.x86_64 is already installed.
Package libzstd-devel-1.5.7-1.fc41.x86_64 is already installed.
Package libxml2-devel-2.12.10-1.fc41.x86_64 is already installed.
Package libffi-devel-3.4.4-7.fc41.x86_64 is already installed.
Dependencies resolved.
========================================================================================================================================
 Package                            Architecture                Version                              Repository                    Size
========================================================================================================================================
Installing:
 libcurl-devel                      x86_64                      8.6.0-10.fc41                        updates                      851 k
 
Transaction Summary
========================================================================================================================================
Install  1 Package
 
Total download size: 851 k
Installed size: 1.2 M
Is this ok [y/N]:
 
Installed:
  libcurl-devel-8.6.0-10.fc41.x86_64
 
Complete!

3. Překlad C3

Nyní, když máme v operačním systému nainstalovány všechny potřebné nástroje i knihovny, můžeme provést druhý důležitý krok. Tím je překlad překladače (sic!) i dalších nástrojů ekosystému programovacího jazyka C3. Nejprve naklonujeme repositář se zdrojovými kódy C3, což je snadné (je vyžadován nástroj Git, který jsme nainstalovali v rámci předchozí kapitoly):

$ git clone https://github.com/c3lang/c3c.git

Výsledkem by měl být nový adresář pojmenovaný – jak jinak – c3c:

Cloning into 'c3c'...
remote: Enumerating objects: 52579, done.
remote: Counting objects: 100% (173/173), done.
remote: Compressing objects: 100% (97/97), done.
remote: Total 52579 (delta 107), reused 81 (delta 74), pack-reused 52406 (from 3)
Receiving objects: 100% (52579/52579), 18.51 MiB | 10.34 MiB/s, done.
Resolving deltas: 100% (38876/38876), done.

Následně do tohoto adresáře přejdeme a zavoláme cmake (další již nainstalovaný nástroj) pro konfiguraci celého procesu překladu:

$ cd c3c
 
$ cmake -B build -S . -DC3_LINK_DYNAMIC=1
 
-- Output to: "/tmp/ramdisk/c3c/build"
-- Configuring done (1.7s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/ramdisk/c3c/build
 

Následuje poslední příkaz, který provede celý překlad:

$ cmake --build build
 
[  1%] Building C object CMakeFiles/miniz.dir/dependencies/miniz/miniz.c.o
[  2%] Linking C static library libminiz.a
[  2%] Built target miniz
[  3%] Building CXX object CMakeFiles/c3c_wrappers.dir/wrapper/src/wrapper.cpp.o
[  4%] Linking CXX static library libc3c_wrappers.a
[  4%] Built target c3c_wrappers
[  5%] Generating git_hash.h
Git Hash: ca2fabc9f9cf511f1abf671b95e6f058ec512f5a
[  6%] Building C object CMakeFiles/c3c.dir/src/build/builder.c.o
...
...
...
[ 96%] Building C object CMakeFiles/c3c.dir/src/compiler/llvm_codegen_value.c.o
[ 97%] Building C object CMakeFiles/c3c.dir/src/compiler/llvm_codegen_storeload.c.o
[ 98%] Building C object CMakeFiles/c3c.dir/src/compiler/llvm_codegen_builtins.c.o
[100%] Linking CXX executable c3c
[100%] Built target c3c

Výsledek by měl být uložen do podadresáře nazvaného build, ve kterém (kromě dalších souborů) nalezneme i spustitelný soubor nazvaný c3c. Ten použijeme v dalším textu:

$ cd build
$ ls -l
total 25664
-rwxr-xr-x.  1 ptisnovs ptisnovs 14267984 Aug 29 11:55 c3c
-rw-r--r--.  1 ptisnovs ptisnovs    23222 Aug 29 11:54 CMakeCache.txt
drwxr-xr-x. 11 ptisnovs ptisnovs      380 Aug 29 11:55 CMakeFiles
-rw-r--r--.  1 ptisnovs ptisnovs     3232 Aug 29 11:54 cmake_install.cmake
-rw-r--r--.  1 ptisnovs ptisnovs       73 Aug 29 11:55 git_hash.h
drwxr-xr-x.  3 ptisnovs ptisnovs       60 Aug 29 11:54 lib
-rw-r--r--.  1 ptisnovs ptisnovs 11505170 Aug 29 11:55 libc3c_wrappers.a
-rw-r--r--.  1 ptisnovs ptisnovs   368636 Aug 29 11:54 libminiz.a
-rw-r--r--.  1 ptisnovs ptisnovs    99155 Aug 29 11:54 Makefile
Poznámka: výsledný spustitelný binární soubor je sice poměrně rozsáhlý, ovšem obsahuje jak samotný překladač, tak i další pomocné nástroje celého ekosystému postaveného okolo jazyka C3.

4. První spuštění překladače

Po (doufejme, že úspěšném) překladu se již můžeme pokusit o spuštění překladače programovacího jazyka C3. Pokud se stále nacházíte v adresáři c3/build, postačuje spustit c3c právě z tohoto adresáře. Z výpisu dostupných příkazů je patrné, že i přes své jméno není c3c „pouhým“ překladačem, ale integruje v sobě i mnohé další nástroje (tím se do jisté míry podobá nástroji go pro stejnojmenný programovací jazyk):

$ ./c3c

Výpis by měl vypadat následovně:

Usage: ./c3c [<options>] <command> [<args>]
 
Commands:
 
  compile <file1> [<file2> ...]                       Compile files without a project into an executable.
  init <project name>                                 Initialize a new project structure.
  init-lib <library name>                             Initialize a new library structure.
  build [<target>]                                    Build the target in the current project.
  benchmark [<target>]                                Run the benchmarks for the target in the current project.
  test [<target>]                                     Run the unit tests for the target in the current project.
  clean                                               Clean all build files.
  run [<target>] [-- [<arg1> ...]]                    Run (and build if needed) the target in the current project.
  dist [<target>]                                     Clean and build a target for distribution.
  clean-run [<target>] [-- [<arg1> ...]]              Clean, then run the target.
  compile-run <file1> [<file2> ...] [-- [<arg1> ...]] Compile files then immediately run the result.
  compile-only <file1> [<file2> ...]                  Compile files but do not perform linking.
  compile-benchmark <file1> [<file2> ...]             Compile files into a test-executable and run benchmarks.
  compile-test <file1> [<file2> ...]                  Compile files into a benchmark-executable and run unit tests.
  static-lib <file1> [<file2> ...]                    Compile files without a project into a static library.
  dynamic-lib <file1> [<file2> ...]                   Compile files without a project into a dynamic library.
  vendor-fetch <library> ...                          Fetches one or more libraries from the vendor collection.
  project <subcommand> ...                            Manipulate or view project files.

Common options:
  -h -hh --help              - Print the help, -h for the normal options, -hh for the full help.
  -V --version               - Print version information.
  -q --quiet                 - Silence unnecessary output.
  -v -vv -vvv                - Verbose output, -v for default, -vv and -vvv gives more information.
  -E                         - Lex only.
  -P                         - Only parse and output the AST as JSON.
  -C                         - Only lex, parse and check.
  -                          - Read code from standard in.
  -o <file>                  - Write output to <file>.
  -O0                        - Safe, no optimizations, emit debug info.
  -O1                        - Safe, high optimization, emit debug info.
  -O2                        - Unsafe, high optimization, emit debug info.
  -O3                        - Unsafe, high optimization, single module, emit debug info.
  -O4                        - Unsafe, highest optimization, relaxed maths, single module, emit debug info, no panic messages.
  -O5                        - Unsafe, highest optimization, fast maths, single module, emit debug info, no panic messages, no backtrace.
  -Os                        - Unsafe, high optimization, small code, single module, no debug info, no panic messages.
  -Oz                        - Unsafe, high optimization, tiny code, single module, no debug info, no panic messages, no backtrace.
  -D <name>                  - Add feature flag <name>.
  -U <name>                  - Remove feature flag <name>.

  --about                    - Prints a short description of C3.
  --build-env                - Prints build environment information (only valid with in combination with a command such as 'compile').
  --run-dir <dir>            - Set the directory from where to run the binary (only for run and compile-run).
  --libdir <dir>             - Add this directory to the c3l library search paths.
  --lib <name>               - Add this c3l library to the compilation.
  --sources <file1> [<file2> ...] - Add these additional sources to the compilation.

  -g                         - Emit debug info.
  -g0                        - Emit no debug info.

  -l <library>               - Link with the static or dynamic library provided.
  -L <library dir>           - Append the directory to the linker search paths.
  -z <argument>              - Send the <argument> as a parameter to the linker.

Use --help or -hh to view the full list of options.
Poznámka: prozatím si v praktické části článku vystačíme pouze se dvěma příkazy, a to konkrétně s příkazem compile a taktéž s příkazem compile-run. Jejich význam by měl být zřejmý; pouze si musíme dát pozor na to, že výsledkem překladu je přímo spustitelný soubor – nemusíme explicitně provádět fázi linkování.

5. Je jazyk C3 zpětně kompatibilní s jazykem C?

Na základě jména C3 i toho, že se na úvodní stránce tohoto jazyka píše „the C-like for programmers who like C“, bychom mohli nabýt dojmu, že je tento jazyk po syntaktické a sémantické stránce s původním céčkem kompatibilní (například tak, jako první verze jazyka C++). Ve skutečnosti tomu tak ovšem není (jde o C-like jazyk), o čemž se můžeme snadno přesvědčit pokusem o překlad libovolného céčkovského zdrojového kódu překladačem c3c. Konkrétně použijeme tento zdrojový kód, na který ještě později navážeme. Pokus o překlad dopadne takto:

1: #include <stdlib.h>
   ^^^^^^^^
(/tmp/ramdisk/c3c/build/renderer.c3:1:1) Error: Expected the start of a global declaration here.
 
 2: #include <string.h>
 3: #include <stdio.h>
 4:
 5: #define NULL_CHECK(value)                                                      \
                                                                                    ^
(/tmp/ramdisk/c3c/build/renderer.c3:5:80) Error: '\' may not be placed outside of a string or comment, did you perhaps forget a " somewhere?
 
 3: #include <stdio.h>
 4:
 5: #define NULL_CHECK(value)                                                      \
 6:     if (value == NULL) {                                                       \
                                                                                   ^
(/tmp/ramdisk/c3c/build/renderer.c3:6:80) Error: '\' may not be placed outside of a string or comment, did you perhaps forget a " somewhere?
 
 4:
 5: #define NULL_CHECK(value)                                                      \
 6:     if (value == NULL) {                                                       \
 7:         fprintf(stderr, "NULL parameter: %s\n", #value);                       \
                                                                                   ^
(/tmp/ramdisk/c3c/build/renderer.c3:7:80) Error: '\' may not be placed outside of a string or comment, did you perhaps forget a " somewhere?
 
 5: #define NULL_CHECK(value)                                                      \
 6:     if (value == NULL) {                                                       \
 7:         fprintf(stderr, "NULL parameter: %s\n", #value);                       \
 8:         return;                                                                \
                                                                                   ^
(/tmp/ramdisk/c3c/build/renderer.c3:8:80) Error: '\' may not be placed outside of a string or comment, did you perhaps forget a " somewhere?
Poznámka: konkrétní rozdíly mezi programovacími jazyky C a C3 budou podrobněji vysvětleny v navazujícím článku; dnes si spíš popisujeme první pocity z práce s C3.

6. Porovnání syntaxe jazyka C s dalšími mainstreamovými programovacími jazyky

Jak jsme si již řekli v úvodní kapitole, patří programovací jazyk C3 do rozsáhlé rodiny takzvaných „céčkovských“ programovacích jazyků, tj. jazyků více či méně inspirovaných jazykem C (typicky ANSI C, pozdější verze C jsou již do jisté míry specifické). Asi nejznámějším příkladem je jazyk C++, který byl dokonce ve svých prvních verzích s céčkem kompatibilní. Podobnost mezi céčkovskými jazyky není pouze sémantická, ale můžeme zde vidět i značnou syntaktickou podobnost.

Ostatně si můžeme ukázat, jak se liší či naopak podobají zápisy stejných algoritmů v různých jazycích z rodiny C. Pro jednoduchost zůstaneme u klasického „školního“ příkladu – výpočtu faktoriálu celého (kladného) čísla. Nerekurzivní varianta psaná v C vypadá takto:

int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

Rekurzivní variantu lze zapsat například takto (plná rekurze):

int factorial_recursive(int n) {
    return n == 0 ? 1 : n * factorial_recursive(n - 1);
}

V jazyce C3 bude zápis jen nepatrně odlišný (viz klíčové slovo fn):

fn int factorial(int n)
{
    int result = 1;
    for (int i = 1; i <= n; ++i)
    {
        result *= i;
    }
    return result;
}

Rekurzivní varianta:

fn int factorial_recursive(int n)
{
    return n == 0 ? 1 : n * factorial_recursive(n - 1);
}

Dalším céčkovským jazykem je jazyk V, ve kterém bude zápis vypadat takto (bez středníků):

fn factorial_recursive(n int) int {
  if n == 0 {
    return 1
  }
  return n * factorial_recursive(n - 1)
}

V programovacím jazyku Zig se taktéž používá klíčové slovo fn. Změnili jsme i datové typy – nyní striktně bezznaménkové:

fn factorial_recursive(x: u32) u32 {
    if (x == 0) {
        return 1;
    } else {
        return factorial_recursive(x - 1) * x;
    }
}

Následuje zápis obou algoritmů v jazyce Go.

Nerekurzivní varianta:

func factorial(n uint) uint {
        if n == 0 {
                return 1
        }
        result := 1
        for i := 1; i <= n; i++ {
                result *= i
        }
        return result
}

Rekurzivní varianta:

func factorial_recursive(n uint64)(result uint64) {
        if (n > 0) {
                result = n * factorial_rerursive(n-1)
                return result
        }
        return 1
}

A konečně se podívejme na jazyk Rust, ve kterém se již rozlišují měnitelné a neměnitelné hodnoty:

fn factorial(number : u32) -> u32{
    let mut factorial : u32 = 1;
 
    for i in 1..(number+1) {
        factorial*=i;
    }
 
    return factorial
}
fn factorial_recursive(number : u32) -> u32{
    if number<=1 {
        return 1;
    }
 
    return number * factorial_recursive(number-1);
}
Poznámka: ve skutečnosti jsou rozdíly mezi těmito jazyky mnohem větší, to však uvidíme až při práci s moduly, strukturovanými datovými typy, pamětí atd.

7. První reálný program vytvořený v jazyku C3

V předchozí kapitole byly ukázány syntaktické rozdíly mezi různými jazyky odvozenými od klasického céčka. Mj. jsme si ukázali i část zdrojového kódu napsaného přímo v jazyku C3. Ovšem nejednalo se o ucelený kód, takže si ho nyní rozšiřme. Pod tímto odstavcem je uveden celý (a přeložitelný) zdrojový kód, po jehož spuštění se vypíše tabulka faktoriálů pro vstupní hodnoty od 0 do 10 (včetně). Povšimněte si zejména způsobu definice funkce klíčovým slovem fn, dále importu modulu std::io (nejvíce diskutovaný rozdíl oproti C) a volání funkce printf z modulu io:

import std::io;
 
fn int factorial(int n)
{
    int result = 1;
    for (int i = 1; i <= n; ++i)
    {
        result *= i;
    }
    return result;
}
 
fn void main()
{
    for (int n = 0; n <= 10; n++)
    {
        io::printf("%d! = %d\n", n, factorial(n));
    }
}

Překlad a následně spuštění programu provedené ve dvou krocích:

$ ./c3c compile factorial.c3
Program linked to executable './factorial'.
 
$ ./factorial
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800

Překlad a následné spuštění programu v jednom kroku:

$ ./c3c compile-run factorial.c3
 
Program linked to executable './factorial'.
Launching ./factorial
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800

8. Makra v programovacím jazyku C3

Poměrně specifickým rysem jazyka C3 je jeho podpora pro tvorbu maker a pochopitelně i pro jejich expanzi v čase překladu. Na rozdíl od (poněkud zjednodušeně řečeno) textových substitucí prováděných preprocesorem jazyka C jsou makra v C3 navržena takovým způsobem, aby jejich tvorba i volání (resp. expanze) byly prováděny bezpečněji. Makra tedy mají plnohodnotné parametry, které lze v textu makra použít bez nutnosti jejich uzávorkování. Taktéž je možné specifikovat typ návratové hodnoty makra atd. Ostatně bez dalších podrobnějších popisů (tomu bude věnován samostatný text) si ukažme makro pro výpočet faktoriálu. To je expandováno již v čase překladu:

import std::io;
 
macro int factorial($n)
{
    $if $n == 0:
        return 1;
    $else
        return $n * factorial($n - 1);
    $endif
}
 
fn void main()
{
    const int N = 10;
    io::printf("%d! = %d\n", N, factorial(N));
}

Překlad programu s jeho spuštěním:

$ ./c3c compile-run factorial_macro.c3
 
Program linked to executable './factorial_macro'.
Launching ./factorial_macro
10! = 3628800
Program completed with exit code 0.
Poznámka: jak již bylo napsáno na začátku této kapitoly, budeme se makry podrobněji zabývat v dalším článku, protože se jedná o dosti rozsáhlé téma.

9. Od C k C3

Dnes nebudeme podrobně popisovat ani syntaxi ani sémantiku jazyka C3. Zvolíme spíše prakticky zaměřený přístup. Konkrétně se pokusíme o transformaci následujícího zdrojového kódu napsaného v ANSI C do jazyka C3. Tento kód po svém spuštění vygeneruje rastrový obrázek s vypočtenou Juliovou množinou:

Juliova množina vykreslená demonstračním příkladem

Obrázek 1: Juliova množina vykreslená demonstračním příkladem napsaným v jazyku C

Autor: tisnik, podle licence: Rights Managed

Povšimněte si, že se ve zdrojovém kódu používají struktury, ukazatele na struktury, základní příkazy pro řízení toku programu (podmínky a smyčky), ukazatele, makra, dynamická alokace paměti a v neposlední řadě taktéž standardní knihovní funkce pro tisk na standardní výstup:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
 
#define NULL_CHECK(value)                                                      \
    if (value == NULL) {                                                       \
        fprintf(stderr, "NULL parameter: %s\n", #value);                       \
        return;                                                                \
    }
 
typedef struct {
    unsigned int width;
    unsigned int height;
} resolution_t;
 
void calc_julia(resolution_t *resolution, const unsigned int maxiter, const unsigned char *palette, double cx, double cy)
{
    double zy0 = -1.5;
    int y;
 
    NULL_CHECK(palette);
 
    puts("P3");
    printf("%d %d\n", resolution->width, resolution->height);
    puts("255");
 
    for (y=0; y < resolution->height; y++) {
        double zx0 = -1.5;
        int x;
        for (x=0; x < resolution->width; x++) {
            double zx = zx0;
            double zy = zy0;
            unsigned int i = 0;
            while (i < maxiter) {
                double zx2 = zx * zx;
                double zy2 = zy * zy;
                if (zx2 + zy2 > 4.0) {
                    break;
                }
                zy = 2.0 * zx * zy + cy;
                zx = zx2 - zy2 + cx;
                i++;
            }
            {
                const unsigned char *color = palette + 3*(i % 256);
                unsigned char r = *color++;
                unsigned char g = *color++;
                unsigned char b = *color;
                printf("%d %d %d\n", r, g, b);
            }
            zx0 += 3.0/resolution->width;
        }
        zy0 += 3.0/resolution->height;
    }
}
 
/* Generate color palette: 256 colors, each color is represented as RGB triple. */
unsigned char *generate_palette(void) {
    unsigned char *palette = (unsigned char *)malloc(256 * 3);
    unsigned char *p = palette;
    int i;
 
    if (palette == NULL) {
        return NULL;
    }
 
    /* fill in by black color */
    memset(palette, 0, 256 * 3);
 
    /* green gradient */
    for (i = 0; i < 32; i++) {
        *p++ = 0;
        *p++ = 4 + i*6;
        *p++ = 0;
    }
 
    /* gradient from green to yellow */
    for (i = 0; i < 32; i++) {
        *p++ = 4 + i * 6;
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 0;
    }
 
    /* gradient from yellow to white */
    for (i = 0; i < 32; i++) {
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 252;
        *p++ = i * 6;
    }
 
    /* gradient from white to yellow */
    for (i = 0; i < 48; i++) {
        *p++ = 252;
        *p++ = 252;
        *p++ = 252 - i * 6;
    }
 
    /* gradient from yellow to green */
    for (i = 0; i < 48; i++) {
        *p++ = 252 - i * 6;
        *p++ = 252;
        *p++ = 0;
    }
 
    /* gradient green to black */
    for (i = 0; i < 48; i++) {
        *p++ = 0;
        *p++ = 252 - i * 6;
        *p++ = 0;
    }
 
    return palette;
}
 
int main(int argc, char **argv)
{
    resolution_t resolution;
    unsigned char *palette = generate_palette();
    int maxiter;
 
    if (argc < 4) {
        puts("usage: ./mandelbrot width height maxiter");
        return 1;
    }
    resolution.width = atoi(argv[1]);
    resolution.height = atoi(argv[2]);
    maxiter = atoi(argv[3]);
    calc_julia(&resolution, maxiter, palette, -0.207190825, 0.676656625);
    return 0;
}

10. Definice datové struktury s rozměry rastrového obrázku

Nejprve se podíváme na to, jakým způsobem se v jazyce C3 definuje datová struktura s popisem rastrového obrázku. V céčku je definice takové struktury následující:

typedef struct {
    unsigned int width;
    unsigned int height;
} resolution_t;

V jazyce C3 je sémantika prakticky stejná, liší se jen způsob zápisu (tedy syntaxe). Odlišují se i jména datových typů – namísto modifikátorů typu int (viz C) přímo použijeme datový typ uint:

struct Resolution {
    uint width;
    uint height;
}
Poznámka: jazyk C3 striktně hlídá název struktury, tj. uživatelského datového typu. Musí začínat velkým písmenem, jinak je nahlášena chyba:
 6: struct resolution {
           ^^^^^^^^^^
(/tmp/ramdisk/c3c/build/renderer_v8.c1:6:8) Error: Names of structs must start with an uppercase letter.

11. Skeleton všech funkcí

Dále do první verze našeho programu dopíšeme skeleton všech tří funkcí, které později budeme implementovat. Povšimněte si, že parametry lze předávat jak hodnotou (maxiter, cx, cy), tak i odkazem (resolution, palette), tedy stejně, jako je tomu v jazyku C. A opět – odlišuje se jen syntaxe, která nyní vyžaduje použití klíčového slova fn na začátku hlaviček funkcí:

fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy) {
}
 
fn char *generate_palette() {
}
 
fn void main()
{
}

Ve skutečnosti by takový program nebylo možné přeložit, protože funkce generate_palette musí vracet nějakou hodnotu (klidně i null):

fn char *generate_palette() {
    char *palette = null;
    return palette;
}

První verze programu pro výpočet Juliovy množiny vypadá následovně:

struct Resolution {
    uint width;
    uint height;
}
 
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}
 
fn char *generate_palette() {
    char *palette = null;
    return palette;
}
 
fn void main()
{
    calc_julia(null, 0, null, 0.0, 0.0);
}
Poznámka: tento program lze přeložit i spustit, ovšem pochopitelně (alespoň prozatím) nic nevykreslí.

12. Anotace parametrů funkcí

V programovacím jazyku C je možné specifikovat, že některé parametry funkce nebudou uvnitř funkce modifikovány. K tomuto účelu se používá klíčové slovo const (u ukazatelů si musíme dát pozor na to, jestli je neměnný ukazatel nebo hodnota, na kterou ukazuje):

void calc_julia(resolution_t *resolution, const unsigned int maxiter, const unsigned char *palette, double cx, double cy)

V jazyku C3 se používá poněkud odlišný přístup. U parametrů typu ukazatel lze s využitím anotací specifikovat, jestli se může (přes předaný ukazatel) provádět zápis, nebo je paměťová oblast určena jen pro čtení (či pro obě operace). Vzhledem k tomu, že ve funkci calc_julia se přes oba ukazatele přistupuje ke struktuře resp. k poli, ze kterého se pouze provádí čtení, můžeme u obou těchto ukazatelů specifikovat příslušnou anotaci. Zápis je na první pohled poněkud zvláštní:

<*
 @param [in] resolution
 @param [in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}

V případě, že se přes ukazatel pokusíme o zápis, povede to k chybě při překladu:

<*
 @param [in] resolution
 @param [in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
    resolution.width=100;
}

Překladač nahlásí chybu:

11: *>
12: fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
13: {
14:     resolution.width=100;
        ^^^^^^^^^^
(/tmp/ramdisk/c3c/build/renderer_v2.c3:14:5) Error: 'in' parameters may not be assigned to.
Poznámka: poněkud předběhneme, ale zápis resolution.width je skutečně syntakticky korektní – nemusíme (a ani nemůžeme) použít operátor ->

Druhá varianta demonstračního příkladu bude vypadat takto:

struct Resolution {
    uint width;
    uint height;
}
 
<*
 @param [in] resolution
 @param [in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}
 
fn char *generate_palette() {
    char *palette = null;
    return palette;
}
 
fn void main()
{
    calc_julia(null, 0, null, 0.0, 0.0);
}

13. Statická kontrola, zda se nepředávají neinicializované ukazatele

Překladač jazyka C3 dokáže provádět (i když prozatím dosti primitivním způsobem) statickou kontrolu, zda se do funkce nepředává neinicializovaný ukazatel. Prozatím umíme specifikovat, že se přes ukazatel nesmí provádět zápisy:

<*
 @param [in] resolution
 @param [in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}

Pokud navíc v anotaci před slovo in vložíme znak ampersandu, znamená to, že ukazatel musí být inicializovaný a nesmí obsahovat hodnotu null:

<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}
Poznámka: použití znaku ampersandu je možná poněkud matoucí, ale na druhou stranu se jedná o céčkovský jazyk, který je plný různých operátorů a paznaků.

Podívejme se nyní na upravený zdrojový kód ukázkového příkladu, v němž je použita upravená anotace ukazatelů předávaných do funkce calc_julia:

struct Resolution {
    uint width;
    uint height;
}
 
<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}
 
fn char *generate_palette() {
    char *palette = null;
    return palette;
}
 
fn void main()
{
    calc_julia(null, 0, null, 0.0, 0.0);
}

Při pokusu o překlad vypíše překladač programovacího jazyka C3 chybu, protože při volání funkce calc_julia do ní skutečně předáváme hodnotu null:

$ ./c3c compile renderer_v3.c3
 
20:
21: fn void main()
22: {
23:     calc_julia(null, 0, null, 0.0, 0.0);
                   ^^^^
(/tmp/ramdisk/c3c/build/renderer_v3.c3:23:16) Error: You may not pass null to the '&' parameter.
 
 9:  @param [&in] resolution
10:  @param [&in] palette
11: *>
12: fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
            ^^^^^^^^^^
(/tmp/ramdisk/c3c/build/renderer_v3.c3:12:9) Note: The definition is here.

14. Kontrola neinicializovaných ukazatelů v čase běhu programu

Mohlo by se zdát, že kontrola neinicializovaných ukazatelů v době překladu aplikace pokryje i následující situaci: stále vyžadujeme, aby byly do funkce calc_julia předávány ukazatele nastavené na nějakou adresu (a ne na none):

<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}

Dále ve funkci generate_palette evidentně vracíme hodnotu null:

fn char *generate_palette() {
    char *palette = null;
    return palette;
}

Tuto hodnotu následně předáváme do funkce calc_julia:

char* palette = generate_palette();
 
calc_julia(&resolution, 0, palette, 0.0, 0.0);

Mohli bychom předpokládat, že v tomto případě překladač jazyka C3 problém odhalí již v době překladu, ale ve skutečnosti tomu tak nebude – k chybě dojde až v době běhu.

Úplný zdrojový kód takto upraveného demonstračního příkladu bude vypadat následovně:

struct Resolution {
    uint width;
    uint height;
}
 
<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}
 
fn char *generate_palette() {
    char *palette = null;
    return palette;
}
 
fn void main()
{
    Resolution resolution;
    resolution.width = 512;
    resolution.height = 512;
 
    char* palette = generate_palette();
 
    calc_julia(&resolution, 0, palette, 0.0, 0.0);
}

Tento zdrojový kód se bez problémů přeloží:

$ ./c3c compile renderer_v4.c3
 
Program linked to executable './renderer_v4'.

Po spuštění dojde k detekci chyby, a to až při volání funkce calc_julia:

$ ./renderer_v4
 
ERROR: 'Reference parameter 'palette' was passed a null pointer argument.'
  in std.core.builtin.default_panic (/tmp/ramdisk/c3c/lib/std/core/builtin.c3:175) [./renderer_v4]
  in renderer_v4.calc_julia (/tmp/ramdisk/c3c/build/renderer_v4.c3:12) [./renderer_v4]
  in renderer_v4.main (/tmp/ramdisk/c3c/build/renderer_v4.c3:29) [./renderer_v4]
  in @main_to_void_main (/tmp/ramdisk/c3c/lib/std/core/private/main_stub.c3:18) [./renderer_v4] [inline]
  in main (/tmp/ramdisk/c3c/build/renderer_v4.c3:21) [./renderer_v4]
  in __libc_start_call_main (source unavailable) [/lib64/libc.so.6]
  in __libc_start_main_alias_2 (source unavailable) [/lib64/libc.so.6]
  in _start (source unavailable) [./renderer_v4]
Illegal instruction (core dumped)
Poznámka: sice dojde k pádu aplikace, ovšem alespoň máme k dispozici výpis volaných funkcí.

15. Přepis funkce pro inicializaci palety a striktnější typová kontrola

Pokusme se nyní do našeho postupně vznikajícího programu doplnit kód funkce generate_palette. Pro alokaci paměti použijeme funkci nazvanou malloc a pro vyplnění pole nulami pak funkci libc::memset (malloc není zapotřebí importovat – je přítomna již ve výchozím jmenném prostoru, ovšem její parametry jsou od céčka odlišné):

char *palette = malloc(256*3);
 
/* fill in by black color */
libc::memset(palette, 0, 256 * 3);

Zatímco v případě jazyka C bylo možné pro naplnění palety – pole bajtů – použít výpočty s hodnotami typu int, v jazyce C3 tomu bude poněkud jinak. O tom se můžeme poměrně snadno přesvědčit:

import std::io;
import libc;
 
struct Resolution {
    uint width;
    uint height;
}
 
<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}
 
fn char *generate_palette() {
    char *palette = malloc(256*3);
    char *p = palette;
 
    /* fill in by black color */
    libc::memset(palette, 0, 256 * 3);
 
    /* green gradient */
    for (int i = 0; i < 32; i++) {
        *p++ = 0;
        *p++ = 4 + i*6;
        *p++ = 0;
    }
 
    /* gradient from green to yellow */
    for (int i = 0; i < 32; i++) {
        *p++ = 4 + i * 6;
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 0;
    }
 
    /* gradient from yellow to white */
    for (int i = 0; i < 32; i++) {
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 252;
        *p++ = i * 6;
    }
 
    /* gradient from white to yellow */
    for (int i = 0; i < 48; i++) {
        *p++ = 252;
        *p++ = 252;
        *p++ = 252 - i * 6;
    }
 
    /* gradient from yellow to green */
    for (int i = 0; i < 48; i++) {
        *p++ = 252 - i * 6;
        *p++ = 252;
        *p++ = 0;
    }
 
    /* gradient green to black */
    for (int i = 0; i < 48; i++) {
        *p++ = 0;
        *p++ = 252 - i * 6;
        *p++ = 0;
    }
 
    return palette;
}
 
fn void main()
{
    Resolution resolution;
    resolution.width = 512;
    resolution.height = 512;
 
    char* palette = generate_palette();
 
    calc_julia(&resolution, 0, palette, 0.0, 0.0);
}

Překladač jazyka C3 nás upozorní na to, že přes ukazatel typu char * není možné zapisovat hodnoty typu int. V tomto ohledu je tedy C3 přísnější, i když ne do takové míry, jako v případě jazyka Go:

24:     /* green gradient */
25:     for (int i = 0; i < 32; i++) {
26:         *p++ = 0;
27:         *p++ = 4 + i*6;
                       ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:27:20) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

 30:
 31:     /* gradient from green to yellow */
 32:     for (int i = 0; i < 32; i++) {
 33:         *p++ = 4 + i * 6;
                        ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:33:20) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

 31:     /* gradient from green to yellow */
 32:     for (int i = 0; i < 32; i++) {
 33:         *p++ = 4 + i * 6;
 34:         *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
                                       ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:34:35) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

 37:
 38:     /* gradient from yellow to white */
 39:     for (int i = 0; i < 32; i++) {
 40:         *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
                                       ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:40:35) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

 39:     for (int i = 0; i < 32; i++) {
 40:         *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
 41:         *p++ = 252;
 42:         *p++ = i * 6;
                    ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:42:16) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

 46:     for (int i = 0; i < 48; i++) {
 47:         *p++ = 252;
 48:         *p++ = 252;
 49:         *p++ = 252 - i * 6;
                          ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:49:22) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

 51:
 52:     /* gradient from yellow to green */
 53:     for (int i = 0; i < 48; i++) {
 54:         *p++ = 252 - i * 6;
                          ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:54:22) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

 59:     /* gradient green to black */
 60:     for (int i = 0; i < 48; i++) {
 61:         *p++ = 0;
 62:         *p++ = 252 - i * 6;
                          ^
(/tmp/ramdisk/c3c/build/renderer_v5.c3:62:22) Error: 'int' cannot implicitly be converted to 'char', but you may use a cast.

16. Výpočty barev v barvové paletě s hodnotami typu char a nikoli int

Aby bylo možné funkci pro výpočet barvové palety přeložit, musíme použít buď explicitní přetypování nebo pozměnit veškeré výpočty takovým způsobem, aby se v nich používaly pouze hodnoty typu char. Tedy například namísto:

/* green gradient */
for (int i = 0; i < 32; i++) {
    *p++ = 0;
    *p++ = 4 + i*6;
    *p++ = 0;
}

programovou smyčku upravíme do podoby:

/* green gradient */
for (char i = 0; i < 32; i++) {
    *p++ = 0;
    *p++ = 4 + i*6;
    *p++ = 0;
}

Výsledný zdrojový kód bude vypadat následovně:

import std::io;
import libc;
 
struct Resolution {
    uint width;
    uint height;
}
 
<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
}
 
fn char *generate_palette() {
    char *palette = malloc(256*3);
    char *p = palette;
 
    /* fill in by black color */
    libc::memset(palette, 0, 256 * 3);
 
    /* green gradient */
    for (char i = 0; i < 32; i++) {
        *p++ = 0;
        *p++ = 4 + i*6;
        *p++ = 0;
    }
 
    /* gradient from green to yellow */
    for (char i = 0; i < 32; i++) {
        *p++ = 4 + i * 6;
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 0;
    }
 
    /* gradient from yellow to white */
    for (char i = 0; i < 32; i++) {
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 252;
        *p++ = i * 6;
    }
 
    /* gradient from white to yellow */
    for (char i = 0; i < 48; i++) {
        *p++ = 252;
        *p++ = 252;
        *p++ = 252 - i * 6;
    }
 
    /* gradient from yellow to green */
    for (char i = 0; i < 48; i++) {
        *p++ = 252 - i * 6;
        *p++ = 252;
        *p++ = 0;
    }
 
    /* gradient green to black */
    for (char i = 0; i < 48; i++) {
        *p++ = 0;
        *p++ = 252 - i * 6;
        *p++ = 0;
    }
 
    return palette;
}
 
fn void main()
{
    Resolution resolution;
    resolution.width = 512;
    resolution.height = 512;
 
    char* palette = generate_palette();
 
    calc_julia(&resolution, 0, palette, 0.0, 0.0);
}

Takto upravený zdrojový kód již bude bez problémů přeložitelný.

17. Volání knihovní I/O funkce a volání nativní céčkovské I/O funkce

Jednou z předností programovacího jazyka C3 je fakt, že je plně podporováno volání nativních céčkovských funkcí, tedy například funkcí ze standardní knihovny atd. Navíc jazyk C3 obsahuje i vlastní knihovní funkce, které lze do jisté míry „mixovat“ s funkcemi céčkovskými. Týká se to i vstupně-výstupního systému, tedy například funkcí určených pro tisk hodnot na standardní výstup atd. Příkladem může být tisk hodnot známou céčkovskou funkcí puts (tisk řetězce) a funkcí io::printf ze standardní knihovny jazyka C3 (jak je z názvu této funkce patrné, je částečně odvozena od své céčkovské varianty, ovšem jedná se o odlišnou implementaci):

import std::io;
 
extern fn int puts(char*);
 
 
 
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
    puts("P3");
    io::printf("%d %d\n", resolution.width, resolution.height);
    puts("255");
}

Výše uvedený fragment kódu by měl po svém zavolání vytisknout hlavičku rastrového obrázku uloženého ve formátu PPM (Portable Pixmap Format, viz též Grafické formáty ve znamení Unixu):

P3
512 512
255
Poznámka: obecně řečeno není vhodný nápad kombinovat I/O funkce nějakého jazyka s céčkovskými I/O funkcemi, protože mohou nastat komplikace při odlišném bufferování, ovšem v tomto případě bude vše pracovat bez problémů.

Náš demonstrační příklad nyní upravíme do takové podoby, aby po svém spuštění alespoň provedl tisk hlavičky rastrového obrázku (prozatím bez obsahu):

import std::io;
import libc;
 
extern fn int puts(char*);
 
struct Resolution {
    uint width;
    uint height;
}
 
<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
    puts("P3");
    io::printf("%d %d\n", resolution.width, resolution.height);
    puts("255");
}
 
fn char *generate_palette() {
    char *palette = malloc(256*3);
    char *p = palette;
 
    /* fill in by black color */
    libc::memset(palette, 0, 256 * 3);
 
    /* green gradient */
    for (char i = 0; i < 32; i++) {
        *p++ = 0;
        *p++ = 4 + i*6;
        *p++ = 0;
    }
 
    /* gradient from green to yellow */
    for (char i = 0; i < 32; i++) {
        *p++ = 4 + i * 6;
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 0;
    }
 
    /* gradient from yellow to white */
    for (char i = 0; i < 32; i++) {
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 252;
        *p++ = i * 6;
    }
 
    /* gradient from white to yellow */
    for (char i = 0; i < 48; i++) {
        *p++ = 252;
        *p++ = 252;
        *p++ = 252 - i * 6;
    }
 
    /* gradient from yellow to green */
    for (char i = 0; i < 48; i++) {
        *p++ = 252 - i * 6;
        *p++ = 252;
        *p++ = 0;
    }
 
    /* gradient green to black */
    for (char i = 0; i < 48; i++) {
        *p++ = 0;
        *p++ = 252 - i * 6;
        *p++ = 0;
    }
 
    return palette;
}
 
fn void main()
{
    Resolution resolution;
    resolution.width = 512;
    resolution.height = 512;
 
    char* palette = generate_palette();
 
    calc_julia(&resolution, 0, palette, 0.0, 0.0);
}

18. Funkční varianta céčkovského programu z deváté kapitoly

Náš program pro výpočet Juliovy množiny je téměř kompletní. Zbývám nám dodat „pouze“ vlastní výpočet, což sice může vypadat komplikovaně, ale tento výpočet se prakticky vůbec nebude lišit od původní céčkovské varianty, takže můžeme programovat dnes tak oblíbeným stylem Copy&Paste :-):

fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
    puts("P3");
    io::printf("%d %d\n", resolution.width, resolution.height);
    puts("255");
 
    double zy0 = -1.5;
    int y;
 
    for (y=0; y < resolution.height; y++) {
        double zx0 = -1.5;
        int x;
        for (x=0; x < resolution.width; x++) {
            double zx = zx0;
            double zy = zy0;
            uint i = 0;
            while (i < maxiter) {
                double zx2 = zx * zx;
                double zy2 = zy * zy;
                if (zx2 + zy2 > 4.0) {
                    break;
                }
                zy = 2.0 * zx * zy + cy;
                zx = zx2 - zy2 + cx;
                i++;
            }
            char *color = palette + 3*(i % 256);
            char r = *color++;
            char g = *color++;
            char b = *color;
            io::printf("%d %d %d\n", r, g, b);
            zx0 += 3.0/resolution.width;
        }
        zy0 += 3.0/resolution.height;
    }
}

Porovnání původní céčkovské varianty a varianty naprogramované v jazyku C3 řádek po řádku:

void calc_julia(resolution_t *resolution, const unsigned int ...    fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{                                                                   {
    double zy0 = -1.5;                                                  double zy0 = -1.5;
    int y;                                                              int y;

    puts("P3");                                                         puts("P3");
    printf("%d %d\n", resolution->width, resolution->height);           io::printf("%d %d\n", resolution.width, resolution.height);
    puts("255");                                                        puts("255");

    for (y=0; y<resolution->height; y++) {                              for (y=0; y<resolution.height; y++) {
        double zx0 = -1.5;                                                  double zx0 = -1.5;
        int x;                                                              int x;
        for (x=0; x<resolution->width; x++) {                               for (x=0; x<resolution.width; x++) {
            double zx = zx0;                                                    double zx = zx0;
            double zy = zy0;                                                    double zy = zy0;
            unsigned int i = 0;                                                 uint i = 0;
            while (i < maxiter) {                                               while (i < maxiter) {
                double zx2 = zx * zx;                                               double zx2 = zx * zx;
                double zy2 = zy * zy;                                               double zy2 = zy * zy;
                if (zx2 + zy2 > 4.0) {                                           if (zx2 + zy2 > 4.0) {
                    break;                                                              break;
                }                                                                   }
                zy = 2.0 * zx * zy + cy;                                            zy = 2.0 * zx * zy + cy;
                zx = zx2 - zy2 + cx;                                                zx = zx2 - zy2 + cx;
                i++;                                                                i++;
            }                                                                   }
            {
                const unsigned char *color = palette + 3*(i % 256);             char *color = palette + 3*(i % 256);
                unsigned char r = *color++;                                     char r = *color++;
                unsigned char g = *color++;                                     char g = *color++;
                unsigned char b = *color;                                       char b = *color;
                printf("%d %d %d\n", r, g, b);                                  io::printf("%d %d %d\n", r, g, b);
            }
            zx0 += 3.0/resolution->width;                                    zx0 += 3.0/resolution.width;
        }                                                                   }
        zy0 += 3.0/resolution->height;                                   zy0 += 3.0/resolution.height;
    }                                                                   }
}                                                                   }

Celý zdrojový kód plně funkčního demonstračního příkladu vypadá následovně:

import std::io;
import libc;
 
extern fn int puts(char*);
 
struct Resolution {
    uint width;
    uint height;
}
 
<*
 @param [&in] resolution
 @param [&in] palette
*>
fn void calc_julia(Resolution *resolution, uint maxiter, char *palette, double cx, double cy)
{
    puts("P3");
    io::printf("%d %d\n", resolution.width, resolution.height);
    puts("255");
 
    double zy0 = -1.5;
    int y;
 
    for (y=0; y < resolution.height; y++) {
        double zx0 = -1.5;
        int x;
        for (x=0; x < resolution.width; x++) {
            double zx = zx0;
            double zy = zy0;
            uint i = 0;
            while (i < maxiter) {
                double zx2 = zx * zx;
                double zy2 = zy * zy;
                if (zx2 + zy2 > 4.0) {
                    break;
                }
                zy = 2.0 * zx * zy + cy;
                zx = zx2 - zy2 + cx;
                i++;
            }
            char *color = palette + 3*(i % 256);
            char r = *color++;
            char g = *color++;
            char b = *color;
            io::printf("%d %d %d\n", r, g, b);
            zx0 += 3.0/resolution.width;
        }
        zy0 += 3.0/resolution.height;
    }
}
 
fn char *generate_palette() {
    char *palette = malloc(256*3);
    char *p = palette;
 
    /* fill in by black color */
    libc::memset(palette, 0, 256 * 3);
 
    /* green gradient */
    for (char i = 0; i < 32; i++) {
        *p++ = 0;
        *p++ = 4 + i*6;
        *p++ = 0;
    }
 
    /* gradient from green to yellow */
    for (char i = 0; i < 32; i++) {
        *p++ = 4 + i * 6;
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 0;
    }
 
    /* gradient from yellow to white */
    for (char i = 0; i < 32; i++) {
        *p++ = i * 2 < 52 ? 200 + i * 2 : 252;
        *p++ = 252;
        *p++ = i * 6;
    }
 
    /* gradient from white to yellow */
    for (char i = 0; i < 48; i++) {
        *p++ = 252;
        *p++ = 252;
        *p++ = 252 - i * 6;
    }
 
    /* gradient from yellow to green */
    for (char i = 0; i < 48; i++) {
        *p++ = 252 - i * 6;
        *p++ = 252;
        *p++ = 0;
    }
 
    /* gradient green to black */
    for (char i = 0; i < 48; i++) {
        *p++ = 0;
        *p++ = 252 - i * 6;
        *p++ = 0;
    }
 
    return palette;
}
 
fn void main()
{
    Resolution resolution;
    resolution.width = 512;
    resolution.height = 512;
 
    char* palette = generate_palette();
 
    calc_julia(&resolution, 1000, palette, -0.207190825, 0.676656625);
}

Po překladu a spuštění tohoto programu by se měl vypočítat a vykreslit následující rastrový obrázek:

Juliova množina vykreslená demonstračním příkladem

Obrázek 2: Juliova množina vykreslená demonstračním příkladem napsaným v jazyku C3

Autor: tisnik, podle licence: Rights Managed
Poznámka: výsledek je binárně zcela totožný s obrázkem vygenerovaným céčkovou variantou rendereru.

19. Repositář s demonstračními příklady

Dnes ukázané demonstrační příklady 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):

# Příklad Stručný popis Adresa
1 factorial.c3 realizace výpočtu faktoriálu https://github.com/tisnik/c3-examples/blob/master/intro­duction/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/intro­duction/factorial_macro.c3
       
3 swap_macro.c3 makro realizující prohození dvou hodnot https://github.com/tisnik/c3-examples/blob/master/intro­duction/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/intro­duction/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/intro­duction/renderer_v1.c3
6 renderer_v2.c3 anotace parametrů funkcí typu ukazatel (pointer) https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v2.c3
7 renderer_v3.c3 statická kontrola, zda se nepředávají neinicializované ukazatele https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v3.c3
8 renderer_v4.c3 runtime kontrola, zda se nepředávají neinicializované ukazatele https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v4.c3
9 renderer_v5.c3 první (nekorektní) varianta funkce pro inicializaci barvové palety https://github.com/tisnik/c3-examples/blob/master/intro­duction/renderer_v5.c3
10 renderer_v6.c3 druhá (korektní) varianta funkce pro inicializaci barvové palety https://github.com/tisnik/c3-examples/blob/master/intro­duction/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/intro­duction/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/intro­duction/renderer_v8.c3

20. Odkazy na Internetu

  1. The C3 Programming Language
    https://c3-lang.org/
  2. C3 For C Programmers
    https://c3-lang.org/language-overview/primer/
  3. 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/Pro­grammingLanguages/comments/o­ohij6/c3_is_a_clike_langu­age_trying_to_be_an/
  4. Tiobe index
    https://www.tiobe.com/tiobe-index/
  5. PYPL PopularitY of Programming Language
    https://pypl.github.io/PYPL.html
  6. C3 Tutorial
    https://learn-c3.org/
  7. History of programming languages
    https://devskiller.com/history-of-programming-languages/
  8. History of programming languages (Wikipedia)
    https://en.wikipedia.org/wi­ki/History_of_programming_lan­guages
  9. D language
    https://dlang.org/
  10. Zig programming language
    https://ziglang.org/
  11. V language
    https://vlang.io/
  12. D programming language
    https://en.wikipedia.org/wi­ki/D_(programming_language)
  13. Zig programming language (Wikipedia)
    https://en.wikipedia.org/wi­ki/Zig_(programming_langu­age)
  14. V programming language (Wikipedia)
    https://en.wikipedia.org/wi­ki/V_(programming_language)
  15. Syntax highlighting for C3's programming language
    https://github.com/Airbus5717/c3.vim
  16. Go factorial
    https://gist.github.com/e­simov/9622710
  17. Generational list of programming languages
    https://en.wikipedia.org/wi­ki/Generational_list_of_pro­gramming_languages
  18. The Language Tree: Almost Every Programming Language Ever Made
    https://github.com/Phileo­sopher/langmap
  19. List of C-family programming languages
    https://en.wikipedia.org/wi­ki/List_of_C-family_programming_languages
  20. Compatibility of C and C++
    https://en.wikipedia.org/wi­ki/Compatibility_of_C_and_C%2B%2B
  21. C++23: compatibility with C
    https://www.sandordargo.com/blog/2023/08/23/cpp­23-c-compatibility
  22. Can C++ Run C Code? Understanding Language Compatibility
    https://www.codewithc.com/can-c-run-c-code-understanding-language-compatibility/
  23. C3: Comparisons With Other Languages
    https://c3-lang.org/faq/compare-languages/
  24. C3 Programming Language Gains Traction as Modern C Alternative
    https://biggo.com/news/202504040125_C3_Pro­gramming_Language_Alterna­tive_to_C
  25. The case against a C alternative
    https://c3.handmade.networ­k/blog/p/8486-the_case_against_a_c_alternative
Neutrální ikona do widgetu na odběr článků ze seriálů

Zajímá vás toto téma? Chcete se o něm dozvědět víc?

Objednejte si upozornění na nově vydané články do vašeho mailu. Žádný článek vám tak neuteče.


Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.