Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Klíčová slova „auto“ a „typeof“ v C23

Дата публикации: 03-08-2026 22:00:10

Jedním z nejvíce konzervativních programovacích jazyků používaných v současnosti je klasické céčko. Součástí specifikace C23 je i rozšíření sémantiky klíčového slova „auto“.

Основное содержимое страницы с новостью.

Jak již bylo zmíněno v perexu dnešního článku, patří programovací jazyk C mezi nejkonzervativnější mainstreamové programovací jazyky vůbec. Mnoho projektů, které v tomto jazyce vznikly, vyžadují minimálně překladač splňující specifikaci ANSI C (C89) nebo její novější variantu C99, což jsou – jak čísla naznačují – již poměrně staré specifikace (ona čísla označují rok ve dvacátém století).

Ovšem i tak konzervativní jazyk, jakým céčko nepochybně je, se neustále vyvíjí, i když by se celý vývoj dal označit jako „mírný pokrok v mezích zákona“. Po specifikaci C99 vznikly specifikace C11, dále C17 a C23. V budoucnu; pokud tedy AI dovolí; by měla další specifikace nést označení C29.

V dnešním článku nás bude nejvíce zajímat specifikace C23, která céčko rozšiřuje o další sémantiku. Možná nejzajímavější změnou je podpora pro (relativně omezenou) typovou inferenci, což je vlastnost, která se objevila v mnoha moderních programovacích jazycích (i když samotná myšlenka typové inference sahá až do doby vzniku jazyka ML).

Jednou z vlastností typové inference je možnost definice proměnné spojené s její inicializací, ovšem tak, že typ proměnné je odvozen právě od přiřazované hodnoty. Pro tento účel bylo v C23 „zneužito“ klíčové slovo auto, které je v céčku používáno od samotných počátků tohoto programovacího jazyka. Nyní ovšem získalo novou sémantiku a současně byla zachována i jeho původní sémantika.

Ostatně nebude na škodu si vypsat klíčová slova, která byla do C23 přidána (mnohá z nich se objevila ve starších překladačích, ovšem neoficiálně):

bool, constexpr, false, nullptr, static_assert, true, typeof, typeof_unqual, thread_local, alignas, alignof

Následuje výpis všech klíčových slov moderního céčka:

alignas, alignof, auto, bool, break, case, char, const, constexpr, continue,
default, do, double, else, enum, extern, false, float, for, goto, if, inline,
int, long, nullptr, register, restrict, return, short, signed, sizeof, static,
static_assert, struct, switch, thread_local, true, typedef, typeof,
typeof_unqual, union, unsigned, void, volatile, while
Krátký pohled do historie: od jazyka BCPL přes B k jazyku C

Vývoj céčka je spojen s vývojem operačního systému UNIX. Při návrhu Unixu se Ken Thompson a jeho kolega Dennis Ritchie oprávněně domnívali, že nově vytvořený operační systém potřebuje také nový systémově orientovaný programovací jazyk, tj. jazyk, ve kterém by bylo možné psát systémové programy a popř. i uživatelské aplikace. Nejprve se sice uvažovalo o využití překladače Fortranu, ovšem tato myšlenka byla vzhledem relativně dosti omezeným hardwarovým možnostem mikropočítače PDP-7 následně opuštěna.

Namísto toho Ken Thompson vytvořil nový programovací jazyk nazvaný jednoduše B. Tento jazyk byl založen na existujícím jazyku BCPL (Basic Combined Programming Language), jenž byl vytvořen Martinem Richardsem již v roce 1966. Vzhledem k tomu, že jazyk BCPL byl z hlediska konstrukce překladače poměrně složitý (což je poněkud paradoxní, neboť se jednalo o zjednodušený jazyk CPL ze začátku šedesátých let), nebylo možné, aby se jeho překladač na mikropočítači PDP-7 spouštěl z operačního systému, protože jen pro samotný překlad zdrojových kódů bylo zapotřebí cca 16 kB operační paměti (což může v roce 2026 způsobit úsměv).

Z tohoto důvodu Ken Thompson z původního jazyka BCPL odstranil některé vlastnosti, které nepovažoval za podstatné (například složitější typy programových smyček) a naopak změnil jeho syntaxi tak, aby se zápis programu mohl co nejvíce zkrátit. Právě z této snahy například pochází i způsob zápisu operátorů pomocí speciálních znaků a nikoli klíčových slov (včetně zcela nových operátorů ++ a –), což je zvyk používaný doposud – viz například současné programovací jazyky, jakými jsou C, C++, Go, Java či JavaScript. Taktéž se v jazyku B objevuje použití středníků pro ukončení příkazů.

Zvláštnost jazyka B spočívá v tom, že překladač jazyka B na PDP-7 i na PDP-11 překládal program nikoli přímo do strojového kódu, ale do bajtkódu, který musel být následně interpretován. Jednalo se v podstatě o stejnou technologii, jaká je použita například u P-kódu (Pascal) či u interpretrů Javovského bajtkódu (dnes se již čisté interpretry tohoto bajtkódu v reálných JRE v praxi nepoužívají).

Klíčové slovo auto v programovacím jazyku B

Zajímavé bude zjistit, jakým způsobem se ovšem vůbec klíčové slovo auto v programovacím jazyku C objevilo. Historie tohoto klíčového slova je ve skutečnosti ještě starší, než samotné céčko, protože auto nalezneme již ve výše zmíněném jazyku B, což je (pochopitelně do určité míry) předchůdce klasického jazyka C. Na programovacím jazyku B bylo zvláštní především to, že podporoval pouze jeden datový typ – slovo (word) – jehož bitová šířka se měnila podle toho, pro jaký procesor byly programy překládány. I adresy v paměti byly reprezentovány pomocí slov (přesněji jako indexy do pole, neboť paměť byla v jazyku B považována za jedno velké lineární pole), což se později ukázalo být problematické, protože na mnoha platformách mají adresy jinou bitovou šířku než slova zpracovávaná procesorem. Nicméně pro účely systémového programování na počítači PDP-7 programovací jazyk do jisté míry B vyhovoval a jeho překladače byly později naprogramovány i pro některé další minipočítače, například pro PDP-11.

To tedy znamená, že v jazyku B není nutné při deklaraci proměnných uvádět jejich datový typ. Na druhou stranu se musel specifikovat způsob uložení proměnné v paměti (v céčku se jedná o storage classes, viz další text). Pro tuto specifikaci se používala klíčová slova extrn (bez „e“) a auto. Uveďme si příklad použití. Jedná se o program pro výpočet číselné řady. V programu se používají lokální proměnné definované na řádku začínající slovem auto (řádek je zvýrazněn). A taktéž se používá několik „externích“ symbolů (externích z pohledu funkce, v céčku je tomu jinak):

/* The following program will calculate the constant e-2 to about
   4000 decimal digits, and print it 50 characters to the line in
   groups of 5 characters.  The method is simple output conversion
   of the expansion
     1/2! + 1/3! + ... = .111....
   where the bases of the digits are 2, 3, 4, . . . */
 
main() {
        extrn putchar, n, v;
        auto i, c, col, a;
 
        i = col = 0;
        while(i<n)
                v[i++] = 1;
        while(col<2*n) {
                a = n+1 ;
                c = i = 0;
                while (i<n) {
                        c =+ v[i] *10;
                        v[i++]  = c%a;
                        c =/ a--;
                }
 
                putchar(c+'0');
                if(!(++col%5))
                        putchar(col%50?' ': '*n');
        }
        putchar('*n*n');
}
 
v[2000];
n 2000;
Specifikace způsobu uložení proměnných v jazyce C

Specifikaci způsobu uložení proměnných (storage class) zdědilo po jazyku B i céčko. Došlo ovšem k rozšíření nabízených možností a ke zpřesnění vlastností jednotlivých způsobů uložení. V jazyku C proto existují čtyři možnosti uložení. Jejich základní vlastnosti jsou vypsány v následující tabulce:

Specifikátor Způsob uložení Výchozí hodnota Rozsah platnosti Doba života proměnné
register registr CPU cokoli v bloku do konce bloku
auto zásobník cokoli v bloku do konce bloku
static datový segment 0 v bloku do konce procesu
extern datový segment 0 globální do konce procesu

S jednotlivými způsoby uložení, resp. se základními vlastnostmi různě definovaných proměnných, se seznámíme v dalších čtyřech kapitolách. Tento popis nám pomůže pochopit jak roli klíčového slova auto, tak i do jisté míry problémy, které nastávají v případě, že je auto sémanticky „přetíženo“ (v C23 má dva významy).

Výchozí způsob uložení proměnných – auto

Nejčastěji se setkáme se způsobem uložení proměnných, který se jmenuje auto. Jedná se o lokální proměnné, při jejichž definici se může použít klíčové slovo auto. Takové proměnné jsou vytvořeny (alokovány) na zásobníku a nejsou implicitně inicializovány, tj. jejich počáteční hodnota může být jakákoli. Platnost a viditelnost takové proměnné je omezena na blok, ve kterém je proměnná definována (i na případné podbloky):

#include <stdio.h>
 
int main(void) {
    auto int x = 10000;
    auto double y = 1.2345;
 
    auto double z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}

Vzhledem k tomu, že u lokálních proměnných je implicitní třída uložení právě auto, je možné při definici proměnných toto klíčové slovo vynechat (což vede k tomu, že mnoho programátorů možná ani neví, že klíčové slovo auto existuje):

#include <stdio.h>
 
int main(void) {
    int x = 10000;
    double y = 1.2345;
 
    double z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}
Definice lokálních proměnných se specifikátorem register

Z dávných hlubin historie pochází i třída uložení register. Proměnné deklarované s touto třídou uložení mají podobné vlastnosti, jako proměnné s uložením auto (viditelnost, implicitní výchozí hodnota není specifikována), ovšem předpokládalo se, že proměnné register budou uloženy v pracovním registru mikroprocesoru. Jedná se tedy vlastně o určitou nápovědu překladači, ovšem moderní překladače dokážou způsob alokace proměnných odvodit mnohdy lépe, než vývojáři (dnešní architektury mikroprocesorů jsou již velmi složité). Nicméně nic nám nebrání klíčové slovo register použít, i když ho moderní překladače s poměrně velkou pravděpodobností budou ignorovat:

#include <stdio.h>
 
int main(void) {
    register int x = 10000;
    register double y = 1.2345;
 
    register double z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}
Definice lokálních proměnných se specifikátorem static

Třída uložení static specifikuje, že hodnoty proměnných jsou uloženy nikoli na zásobníku, ale v datovém segmentu, což v důsledku znamená, že hodnota proměnné může „přežít“ platnost bloku, ve kterém je proměnná specifikována. Výchozí hodnota těchto proměnných (pokud není určeno jinak) je nulová. Tato třída uložení je výchozí pro proměnné definované na globální úrovni (tedy nikoli ve funkci), ovšem můžeme ji použít i pro lokální proměnné – klíčové slovo static je v tomto případě povinné.

Pokusme se pro všechny tři proměnné použité v naší funkci main určit třídu uložení static:

#include <stdio.h>
 
int main(void) {
    static int x = 10000;
    static double y = 1.2345;
 
    static double z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}

Tento program ovšem není korektní, protože překladač nedokáže vypočítat výchozí (explicitně zadanou) hodnotu proměnné z – výraz není (z pohledu překladače) konstantou:

$ gcc -Wall -pedantic 04_static_storage_class.c
 
04_static_storage_class.c: In function ‘main’:
04_static_storage_class.c:7:23: error: initializer element is not constant
    7 |     static double z = y * x;
      |                       ^

Podobnou zprávu vypíše i clang:

$ clang 04_static_storage_class.c
 
04_static_storage_class.c:7:25: error: initializer element is not a compile-time constant
    7 |     static double z = y * x;
      |                       ~~^~~
1 error generated.

Korektní varianta programu deklaruje pouze dvě statické proměnné x a y, zatímco proměnná z má třídu uložení auto:

#include <stdio.h>
 
int main(void) {
    static int x = 10000;
    static double y = 1.2345;
 
    double z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}
Definice lokálních a globálních proměnných s třídou uložení extern: nekorektní varianty

Poslední třída uložení proměnných se označuje klíčovým slovem extern. Takto označené proměnné jsou, stejně jako statické proměnné, uloženy v datovém segmentu, ovšem jejich rozsah platnosti je vždy globální a životnost těchto proměnných sahá až ke konci procesu. Zvláštnost extern spočívá v tom, že jím překladači pouze oznamujeme „na tomto místě chci používat proměnnou xyz“; nejedná se o definici proměnné, která by vedla k alokaci paměti na zásobníku nebo v datovém segmentu (tedy jde o deklaraci, ne o definici). To znamená, že proměnná se stejným jménem musí být definována v jiném místě programu, aby pro ni bylo zajištěno umístění v paměti.

Ostatně si to můžeme snadno otestovat. Upravíme náš program do takové podoby, že u všech lokálních proměnných určíme třídu uložení extern (deklarace proměnných – překladač bude vědět o jejich jméně a typu, nikoli o umístění):

#include <stdio.h>
 
int main(void) {
    extern int x = 10000;
    extern double y = 1.2345;
 
    double z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}

Takový program je ovšem nekorektní, na což nás překladače upozorní:

$ gcc -pedantic -Wall 06_extern_storage_class.c
 
06_extern_storage_class.c: In function ‘main’:
06_extern_storage_class.c:4:16: error: ‘x’ has both ‘extern’ and initializer
    4 |     extern int x = 10000;
      |                ^
06_extern_storage_class.c:5:19: error: ‘y’ has both ‘extern’ and initializer
    5 |     extern double y = 1.2345;
      |                   ^

popř. pro jiný překladač:

$ clang 06_extern_storage_class.c
 
06_extern_storage_class.c:4:16: error: declaration of block scope identifier with linkage cannot have an initializer
    4 |     extern int x = 10000;
      |                ^
06_extern_storage_class.c:5:19: error: declaration of block scope identifier with linkage cannot have an initializer
    5 |     extern double y = 1.2345;
      |                   ^
2 errors generated.

Problém ovšem primárně nespočívá v přiřazení do externích proměnných, jak chybová hlášení naznačují. Pokud by tomu tak bylo, byl by následující program korektní. Ovšem ve skutečnosti tomu tak není, protože chybí definice proměnných:

#include <stdio.h>
 
extern int x;
extern double y;
extern double z;
 
int main(void) {
 
    x = 10000;
    y = 1.2345;
 
    z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}

Vzhledem k tomu, že proměnné nejsou definovány, nastane problém ve fázi linkování (linker je nenalezne):

$ gcc -pedantic -Wall 07_extern_storage_class.c
 
/usr/bin/ld: /tmp/cciMwEEb.o: in function `main':
07_extern_storage_class.c:(.text+0x6): undefined reference to `x'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x1a): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x20): undefined reference to `x'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x30): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x3c): undefined reference to `z'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x44): undefined reference to `z'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x4b): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x51): undefined reference to `x'
collect2: error: ld returned 1 exit status

Dtto pro Clang:

$ clang 07_extern_storage_class.c
 
/usr/bin/ld: /tmp/07_extern_storage_class-3a8400.o: in function `main':
07_extern_storage_class.c:(.text+0x12): undefined reference to `x'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x27): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x30): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x39): undefined reference to `x'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x46): undefined reference to `z'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x4d): undefined reference to `x'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x56): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x5f): undefined reference to `z'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Korektní příklad použití třídy uložení extern a malý kvíz na závěr

Víme již, že proměnné s třídou uložení extern mohou být několikrát deklarovány, ovšem současně musí být (jedenkrát!) definovány. Tento postup je ukázán v dalším demonstračním příkladu, který se skládá ze dvou zdrojových souborů. V prvním souboru se pouze pracuje s externími proměnnými, pro něž se ale při překladu tohoto souboru nealokuje paměť:

#include <stdio.h>
 
extern int x;
extern double y;
 
int main(void) {
    double z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}

Teprve ve druhém souboru jsou proměnné korektně definovány a dokonce i explicitně inicializovány:

int x = 10000;
double y = 1.2345;

Překlad nyní proběhne bez chyby:

$ gcc -Wall -std=c89 -pedantic -o 08_extern_storage_class 08_extern_storage_class.c 08_vars.c

A program bude spustitelný:

$ ./08_extern_storage_class
 
10000 x 1.234500 = 12345.000000

Na závěr první poloviny článku se pokuste odpovědět, jaké hodnoty se vypíšou po překladu a spuštění následujícího programu:

#include <stdio.h>
 
void foo() {
    extern int x;
    x = 2;
    printf("%d ", x);
}
 
int x = 1;
 
int main() {
    foo();
    printf("%d", x);
    return 0;
}

Možné odpovědi:

  1. 1 1
  2. 1 2
  3. 2 1
  4. 2 2
  5. vznikne chyba při překladu
  6. vznikne běhová chyba
Typová inference v C23

Konečně se dostáváme k technologii typové inference, která byla oficiálně do programovacího jazyka C zavedena v rámci specifikace C23. Překladače splňující tuto normu dokážou odvodit typy proměnných na základě přiřazovaných hodnot. Ovšem tyto proměnné musí být označeny klíčovým slovem auto (což je vlastně pseudotyp), které zde dostalo nový význam (již se nejedná pouze o specifikaci třídy uložení, ale vlastně o obdobu klíčového slova var či let).

Podívejme se na základní způsob použití. Zatímco v předchozích demonstračních příkladech jsme explicitně uváděli, že proměnná x je typu int a proměnné y a z jsou typu double, v C23 tyto informace můžeme vynechat. Typy proměnných x a y jsou odvozeny z přiřazované konstanty, zatímco typ proměnné z je odvozen na základě specifikace céčka (ta říká, jak se provádí součin celého čísla a čísla s plovoucí řádovou čárkou):

#include <stdio.h>
 
int main(void) {
    auto x = 10000;
    auto y = 1.2345;
 
    auto z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}

Ověřme si, že výpočet je proveden korektně. Překlad by měl proběhnout bez chyby (povšimněte si, že pro jistotu explicitně zadávám standard céčka):

$ make 12_type_inference_2
 
gcc -Wall -std=c23 -pedantic -o 12_type_inference_2 12_type_inference_2.c

Spuštění příkladu:

$ ./12_type_inference_2
 
10000 x 1.234500 = 12345.000000

Poznámka: v případě Clangu je nutné explicitně zadat specifikaci C, tj. použít -std=c23.

Kompatibilita se staršími verzemi jazyka C

Demonstrační příklad uvedený v předchozí kapitole se v překladači splňujícím specifikaci C23 evidentně přeložil korektně. Ovšem bude zajímavé zjistit, jak stejný zdrojový kód pochopí překladač splňující C89/C90. To lze snadno ověřit:

$ gcc -std=c89 -o 11_type_inference_1 11_type_inference_1.c
 
$ echo $?
0

Zdrojový kód se bez problémů přeloží (!), ovšem výsledky nebudou příliš ohromující:

$ ./11_type_inference_1
 
10000 x 0.000000 = 0.000000

Proč tomu tak je? Zápisy:

    auto x = 10000;
    auto y = 1.2345;

jsou pochopeny jako:

    auto int x = 10000;
    auto int y = 1.2345;

protože výchozím typem je int (zděděno z původního K&R C).

Lepší (a to v každém případě, bez ohledu na C23 atd.) je pochopitelně použití přepínačů -Wall, -pedantic apod. Nyní nám překladač po právu vynadá:

$ make 11_type_inference_1
 
gcc -Wall -std=c89 -pedantic -o 11_type_inference_1 11_type_inference_1.c
11_type_inference_1.c: In function ‘main’:
11_type_inference_1.c:4:10: warning: type defaults to ‘int’ in declaration of ‘x’ [-Wimplicit-int]
    4 |     auto x = 10000;
      |          ^
11_type_inference_1.c:5:10: warning: type defaults to ‘int’ in declaration of ‘y’ [-Wimplicit-int]
    5 |     auto y = 1.2345;
      |          ^
11_type_inference_1.c:7:10: warning: type defaults to ‘int’ in declaration of ‘z’ [-Wimplicit-int]
    7 |     auto z = y * x;
      |          ^
11_type_inference_1.c:9:19: warning: format ‘%f’ expects argument of type ‘double’, but argument 3 has type ‘int’ [-Wformat=]
    9 |     printf("%d x %f = %f\n", x, y, z);
      |                  ~^             ~
      |                   |             |
      |                   double        int
      |                  %d
11_type_inference_1.c:9:24: warning: format ‘%f’ expects argument of type ‘double’, but argument 4 has type ‘int’ [-Wformat=]
    9 |     printf("%d x %f = %f\n", x, y, z);
      |                       ~^           ~
      |                        |           |
      |                        double      int
      |                       %d

Poznámka: druhým pravidlem by měl být stav, kdy se projekt přeloží bez varování. Potom se nové varování neztratí mezi záplavou „známých“ varování (příkladem je jazyk Go, ve kterém se autoři snažili konceptu varování zcela vyhnout).

Jakého typu jsou proměnné s odvozenými typy?

Bude zajímavé zjistit, zda překladač skutečně odvodil typy proměnných x, y a z korektně. Jak toho však dosáhnout, když jazyk C nepodporuje zjištění informací o typech za běhu runtime type identification – RTTI)? Můžeme zde využít konstrukci _Generic (definována již v C11, tedy nejde o žádnou žhavou novinku). Tato konstrukce se vyhodnocuje v čase překladu a nezávisí tedy na RTTI. Funguje tak, že na základě typu prvního předaného výrazu vrátí výraz zapsaný v jedné z větví nebo z poslední větve default:

_Generic((výraz), typ1: výraz1, typ2: výraz2, default: výraz_default)

Je to sice poněkud kostrbaté, ale můžeme si napsat makro, které ověří, jestli je výraz (v našem případě proměnná) konkrétního typu:

#define IS_TYPE(expr, T) _Generic((expr), T: "yes", default: "no")

Celý zdrojový kód příkladu se upraví do následující podoby – u každé proměnné budeme testovat, které je typu (z několika předem „vytipovaných typů“):

#include <stdio.h>
 
#define IS_TYPE(expr, T) _Generic((expr), T: "yes", default: "no")
 
int main(void) {
    auto x = 10000;
    auto y = 1.2345;
 
    auto z = y * x;
 
    printf("x is bool:   %s\n", IS_TYPE(x, bool));
    printf("x is int:    %s\n", IS_TYPE(x, int));
    printf("x is float:  %s\n", IS_TYPE(x, float));
    printf("x is double: %s\n", IS_TYPE(x, double));
    printf("\n");
 
    printf("y is bool:   %s\n", IS_TYPE(y, bool));
    printf("y is int:    %s\n", IS_TYPE(y, int));
    printf("y is float:  %s\n", IS_TYPE(y, float));
    printf("y is double: %s\n", IS_TYPE(y, double));
    printf("\n");
 
    printf("z is bool:   %s\n", IS_TYPE(z, bool));
    printf("z is int:    %s\n", IS_TYPE(z, int));
    printf("z is float:  %s\n", IS_TYPE(z, float));
    printf("z is double: %s\n", IS_TYPE(z, double));
    printf("\n");
 
    return 0;
}

Vyhodnocení IS_TYPE proběhne v čase překladu. Výsledky by měly vypadat následovně:

$ make 13_check_type
 
gcc -Wall -std=c23 -pedantic -o 13_check_type 13_check_type.c
 
 
 
$ ./13_check_type
 
x is bool:   no
x is int:    yes
x is float:  no
x is double: no
 
y is bool:   no
y is int:    no
y is float:  no
y is double: yes
 
z is bool:   no
z is int:    no
z is float:  no
z is double: yes
Typová inference a ukazatele

Překladač jazyka C dokáže při vyhodnocování typů proměnných pracovat i s ukazateli. Pokusme se tedy upravit náš demonstrační příklad do takové podoby, ve které se definuje proměnná x (s typovou inferencí), dále se definuje ukazatel na tuto proměnnou y (opět s typovou inferencí) a ještě jeden ukazatel z, jenž ukazuje na y (opět s využitím typové inference). Překladač má tedy k dispozici jen jedinou informaci – konstantu 10000. Z ní musí odvodit typy všech tří proměnných:

#include <stdio.h>
 
int main(void) {
    auto x = 10000;
    auto y = &x;
    auto z = &y;
 
    printf("  x: %d\n", x);
    printf("  y: %p\n", (void*)y);
    printf(" *y: %d\n", *y);
    printf("  z: %p\n", (void*)z);
    printf(" *z: %p\n", (void*)*z);
    printf("**z: %d\n", **z);
 
    return 0;
}

Pokusme se tento program přeložit a spustit:

$ make 14_pointers
 
gcc -Wall -std=c23 -pedantic -o 14_pointers 14_pointers.c
 
 
 
$ ./14_pointers 
 
  x: 10000
  y: 0x7ffe392bf964
 *y: 10000
  z: 0x7ffe392bf958
 *z: 0x7ffe392bf964
**z: 10000

Z vypsaných výsledků je patrné, že dereference přes yz pracuje korektně (adresy uložené v ukazatelích se pochopitelně mohou lišit).

Konstrukce typeof

Jak už to v IT bývá, znamená jedno vylepšení (v tomto případě zavedení typové inference) vznik nových problémů. Konkrétně v případě typové inference je v některých případech nutné mít možnost definovat novou proměnnou, jejíž typ je stejný, jako typ jiné proměnné (nebo dokonce výrazu, který dokáže překladač vyhodnotit). Potřebujeme tedy mít schopnost následujícího zápisu definice proměnné bar s typem, který je stejný, jako typ proměnné foo:

stejný_typ_jako_proměnná_foo bar;

Před zavedením typové inference to nebyl větší problém – postačilo totiž použít typedef nebo oblíbené copy&paste. Ovšem jak se postavit k tomu, že proměnná foo je definována takto?:

auto foo = nějaký_výraz;

K vyhodnocení typu proměnné/výrazu se používá právě konstrukce typeof:

typeof(foo) bar;

Poznámka: v tomto případě se nejedná o typovou inferenci, takže nejsme „nuceni“ proměnnou inicializovat.

Kombinaci auto a typeof si můžeme snadno otestovat v následujícím příkladu. Dvě proměnné x a y mají typ odvozený typovou inferencí, u třetí proměnné z pak explicitně říkáme, že má být stejného typu, jako proměnná y:

#include <stdio.h>
 
int main(void) {
    auto x = 10000;
    auto y = 1.2345;
 
    typeof(y) z = y * x;
 
    printf("%d x %f = %f\n", x, y, z);
    return 0;
}

Otestování:

$ make 15_typeof
 
gcc -Wall -std=c23 -pedantic -o 15_typeof 15_typeof.c
 
 
 
$ ./15_typeof
 
10000 x 1.234500 = 12345.000000
Kontrola typů proměnné s typem získaným pomocí typeof

V dnešním posledním demonstračním příkladu si ověříme, jaký je tedy vlastně konkrétní typ proměnných x, y a z v případě, že dva typy jsou získány typovou inferencí a třetí je vyhodnocen překladačem přes konstrukci typeof:

#include <stdio.h>
 
#define IS_TYPE(expr, T) _Generic((expr), T: "yes", default: "no")
 
int main(void) {
    auto x = 10000;
    auto y = 1.2345;
 
    typeof(y) z = y * x;
 
    printf("x is bool:   %s\n", IS_TYPE(x, bool));
    printf("x is int:    %s\n", IS_TYPE(x, int));
    printf("x is float:  %s\n", IS_TYPE(x, float));
    printf("x is double: %s\n", IS_TYPE(x, double));
    printf("\n");
 
    printf("y is bool:   %s\n", IS_TYPE(y, bool));
    printf("y is int:    %s\n", IS_TYPE(y, int));
    printf("y is float:  %s\n", IS_TYPE(y, float));
    printf("y is double: %s\n", IS_TYPE(y, double));
    printf("\n");
 
    printf("z is bool:   %s\n", IS_TYPE(z, bool));
    printf("z is int:    %s\n", IS_TYPE(z, int));
    printf("z is float:  %s\n", IS_TYPE(z, float));
    printf("z is double: %s\n", IS_TYPE(z, double));
    printf("\n");
 
    return 0;
}

Výsledky odpovídají očekávání:

$ make 16_typeof_type
 
gcc -Wall -std=c23 -pedantic -o 16_typeof_type 16_typeof_type.c
 
 
 
$ ./16_typeof_type
x is bool:   no
x is int:    yes
x is float:  no
x is double: no
 
y is bool:   no
y is int:    no
y is float:  no
y is double: yes
 
z is bool:   no
z is int:    no
z is float:  no
z is double: yes
Shrnutí

Vzhledem k tomu, že se nějaká forma typové inference stala součástí prakticky všech mainstreamových programovacích jazyků (resp. těch jazyků, v nichž se typy berou vážně), bylo jen otázkou času, kdy se tato vlastnost dostane i do konzervativního jazyka C. Řešení zvolené v rámci specifikace C23 není špatné, ovšem kvůli tomu, že klíčové slovo auto má (nyní) dva významy a ve spojení s chováním definovaným ve starších specifikacích jazyka C je více než dobrý nápad provádět překlady s přepínači -Wall a -pedantic, popř. je v případě potřeby doplnit i o přepínač -Wconversion.

Poznámka: osobně ale stejně zůstávám u C89 :-)

Příloha A: reálný vliv slova register ve vybraném překladači céčka

V předchozím textu jsme se mj. zmínili i o existenci klíčového slova register, které bylo do céčka přidáno (především) z toho důvodu, aby bylo možné překladači „napovědět“, které proměnné mohou být uloženy do pracovních registrů mikroprocesoru. Přístup k takovým proměnným je pochopitelně mnohem (dnes řádově) rychlejší, než přístup k hodnotám uloženým v operační paměti resp. ve vyrovnávací paměti (cache). Většina moderních překladačů ovšem toto klíčové slovo prakticky kompletně ignoruje, protože překladače dnes dokážou provádět velmi kvalitní analýzu zdrojových kódů a samy určit, které proměnné budou uloženy v registrech (a na jak dlouho).

Ovšem stále existují překladače céčka, které pro toto klíčové slovo mají využití. Typicky se jedná o překladače určené pro takové architektury mikroprocesorů a mikrořadičů, jenž nebyly navrženy s ohledem na vyšší programovací jazyky (tedy například mají malý počet registrů, registry mají specializované určení atd.). Velmi dobrým příkladem je osmibitový mikroprocesor MOS 6502 a céčkový překladač (resp. přesněji řečeno crosspřekladač) cc65. Ten umožňuje klíčové slovo register chápat následovně:

  • Běžně jsou lokální proměnné ukládány na zásobník (i když to pro MOS 6502 je dosti netypické). Nejedná se ovšem nutně o HW zásobník (první stránka paměti).
  • Lokální proměnné označené jako register jsou uloženy v malé oblasti paměti (na nulté stránce), která slouží jako množina registrů. Tato oblast je relativně malá, ovšem to nemusí vadit, protože u mnoha mikroprocesorů je relativně malá i sada pracovních registrů.

Poznámka: MOS 6502 sice obsahuje „skutečné“ registry, ale je jich naprosté minimum – akumulátor A a dva index registry X a Y. Tj. v překladači C65 tedy klíčové slovo register neznamená „registr CPU“, ale spíše „dobře dostupné buňky operační paměti pro dočasné použití“.

Ostatně můžeme si otestovat, jakým způsobem se přeloží následující dvě funkce. Nejsou provedeny optimalizace, takže překladač funkce nebo jejich těla neodstraní, i když by to v tomto případě klidně udělat mohl.

První funkce provádí výpočty s lokálními proměnnými, u kterých se implicitně předpokládá třída uložení auto:

void main(void) {
    unsigned char a;
    unsigned char b;
    unsigned char c;
    a = 10;
    b = 20;
    c = a + b;
}

Druhá funkce je prakticky totožná, ovšem proměnné mají třídu uložení register a nikoli auto:

void main(void) {
    register unsigned char a;
    register unsigned char b;
    register unsigned char c;
    a = 10;
    b = 20;
    c = a + b;
}

Překladač CC65 skutečně klíčové slovo register může využít. Pro lokální proměnné s třídou uložení auto se takové proměnné ukládají na zásobník (což nemusí být nutně HW zásobník):

;
; File generated by cc65 v 2.18 - Fedora 2.19-12.fc42
;
        .fopt           compiler,"cc65 v 2.18 - Fedora 2.19-12.fc42"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
        jsr     decsp3
        ldx     #$00
        lda     #$0A
        ldy     #$02
        sta     (sp),y
        ldx     #$00
        lda     #$14
        ldy     #$01
        sta     (sp),y
        ldy     #$02
        ldx     #$00
        lda     (sp),y
        jsr     pushax
        ldy     #$03
        ldx     #$00
        lda     (sp),y
        jsr     tosaddax
        ldx     #$00
        ldy     #$00
        sta     (sp),y
        jsr     incsp3
        rts
 
.endproc

Naopak lokální proměnné s třídou uložení register budou uloženy do nulté stránky paměti, tj. na takové místo, které nabízí rychlý přístup pro čtení i zápis:

Školení Hacking

;
; File generated by cc65 v 2.18 - Fedora 2.19-12.fc42
;
        .fopt           compiler,"cc65 v 2.18 - Fedora 2.19-12.fc42"
        .setcpu         "6502"
        .smart          on
        .autoimport     on
        .case           on
        .debuginfo      off
        .importzp       sp, sreg, regsave, regbank
        .importzp       tmp1, tmp2, tmp3, tmp4, ptr1, ptr2, ptr3, ptr4
        .macpack        longbranch
        .forceimport    __STARTUP__
        .export         _main
 
; ---------------------------------------------------------------
; void __near__ main (void)
; ---------------------------------------------------------------
 
.segment        "CODE"
 
.proc   _main: near
 
.segment        "CODE"
 
        lda     regbank+5
        jsr     pusha
        lda     regbank+4
        jsr     pusha
        lda     regbank+3
        jsr     pusha
        ldx     #$00
        lda     #$0A
        sta     regbank+5
        ldx     #$00
        lda     #$14
        sta     regbank+4
        ldx     #$00
        lda     regbank+5
        jsr     pushax
        ldx     #$00
        lda     regbank+4
        jsr     tosaddax
        ldx     #$00
        sta     regbank+3
        ldy     #$00
L0008:  lda     (sp),y
        sta     regbank+3,y
        iny
        cpy     #$03
        bne     L0008
        jsr     incsp3
        rts
 
.endproc

Poznámka: ve skutečnosti jsou oba výsledky překladu dosti neoptimální, což je jeden z důvodů, proč se pro MOS 6502 céčko příliš často nepoužívá. Tento mikroprocesor skutečně nebyl na tento jazyk připraven (to se ovšem do značné míry týká i Pascalu, jenž je též postaven na myšlence lokálních proměnných ukládaných na zásobník).

Příloha B: Makefile pro překlad všech demonstračních příkladů

Všechny dnes popsané demonstrační příklady, pro jejichž překlad je zapotřebí použít gcc, je možné přeložit s využitím souboru Makefile, jehož obsah je vypsán pod tímto odstavcem:

# použitý překladač céčka
CC=gcc
 
# parametry překladače pro specifikaci C89
C89_FLAGS=-Wall -std=c89 -pedantic
 
# parametry překladače pro specifikaci C89
C23_FLAGS=-Wall -std=c23 -pedantic
 
programs := \
            01_no_auto_keyword \
            02_auto_storage_class \
            03_register_storage_class \
            04_static_storage_class \
            05_static_storage_class \
            06_extern_storage_class \
            07_extern_storage_class \
            08_extern_storage_class \
            11_type_inference_1 \
            12_type_inference_2 \
            13_check_type \
            14_pointers \
            15_typeof \
            16_typeof_type
 
all: $(programs)
 
clean:
        rm -f $(programs)
 
.PHONY: all clean
 
01_no_auto_keyword:     01_no_auto_keyword.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
02_auto_storage_class:  02_auto_storage_class.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
03_register_storage_class:      03_register_storage_class.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
04_static_storage_class:        04_static_storage_class.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
05_static_storage_class:        05_static_storage_class.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
06_extern_storage_class:        06_extern_storage_class.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
07_extern_storage_class:        07_extern_storage_class.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
08_extern_storage_class:        08_extern_storage_class.c 08_vars.c
        $(CC) $(C89_FLAGS) -o $@ $^
 
11_type_inference_1:    11_type_inference_1.c
        $(CC) $(C89_FLAGS) -o $@ $<
 
12_type_inference_2:    12_type_inference_2.c
        $(CC) $(C23_FLAGS) -o $@ $<
 
13_check_type:  13_check_type.c
        $(CC) $(C23_FLAGS) -o $@ $<
 
14_pointers:    14_pointers.c
        $(CC) $(C23_FLAGS) -o $@ $<
 
15_typeof:      15_typeof.c
        $(CC) $(C23_FLAGS) -o $@ $<
 
16_typeof_type: 16_typeof_type.c
        $(CC) $(C23_FLAGS) -o $@ $<

Poznámka: některé příklady jsou schválně napsány takovým způsobem, aby je nebylo možné přeložit. Z tohoto důvodu je nutné spouštět make s přepínačem –keep-going. Ostatně si můžeme ukázat, jako to vypadá v praxi:

$ make --keep-going
 
gcc -Wall -std=c89 -pedantic -o 01_no_auto_keyword 01_no_auto_keyword.c
gcc -Wall -std=c89 -pedantic -o 02_auto_storage_class 02_auto_storage_class.c
gcc -Wall -std=c89 -pedantic -o 03_register_storage_class 03_register_storage_class.c
gcc -Wall -std=c89 -pedantic -o 04_static_storage_class 04_static_storage_class.c
04_static_storage_class.c: In function ‘main’:
04_static_storage_class.c:7:23: error: initializer element is not constant
    7 |     static double z = y * x;
      |                       ^
make: *** [Makefile:43: 04_static_storage_class] Error 1
gcc -Wall -std=c89 -pedantic -o 05_static_storage_class 05_static_storage_class.c
gcc -Wall -std=c89 -pedantic -o 06_extern_storage_class 06_extern_storage_class.c
06_extern_storage_class.c: In function ‘main’:
06_extern_storage_class.c:4:16: error: ‘x’ has both ‘extern’ and initializer
    4 |     extern int x = 10000;
      |                ^
06_extern_storage_class.c:5:19: error: ‘y’ has both ‘extern’ and initializer
    5 |     extern double y = 1.2345;
      |                   ^
make: *** [Makefile:49: 06_extern_storage_class] Error 1
gcc -Wall -std=c89 -pedantic -o 07_extern_storage_class 07_extern_storage_class.c
/usr/bin/ld: /tmp/ccpGO5Uv.o: in function `main':
07_extern_storage_class.c:(.text+0x6): undefined reference to `x'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x1a): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x20): undefined reference to `x'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x30): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x3c): undefined reference to `z'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x44): undefined reference to `z'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x4b): undefined reference to `y'
/usr/bin/ld: 07_extern_storage_class.c:(.text+0x51): undefined reference to `x'
collect2: error: ld returned 1 exit status
make: *** [Makefile:52: 07_extern_storage_class] Error 1
gcc -Wall -std=c89 -pedantic -o 08_extern_storage_class 08_extern_storage_class.c 08_vars.c
gcc -Wall -std=c89 -pedantic -o 11_type_inference_1 11_type_inference_1.c
11_type_inference_1.c: In function ‘main’:
11_type_inference_1.c:4:10: warning: type defaults to ‘int’ in declaration of ‘x’ [-Wimplicit-int]
    4 |     auto x = 10000;
      |          ^
11_type_inference_1.c:5:10: warning: type defaults to ‘int’ in declaration of ‘y’ [-Wimplicit-int]
    5 |     auto y = 1.2345;
      |          ^
11_type_inference_1.c:7:10: warning: type defaults to ‘int’ in declaration of ‘z’ [-Wimplicit-int]
    7 |     auto z = y * x;
      |          ^
11_type_inference_1.c:9:19: warning: format ‘%f’ expects argument of type ‘double’, but argument 3 has type ‘int’ [-Wformat=]
    9 |     printf("%d x %f = %f\n", x, y, z);
      |                  ~^             ~
      |                   |             |
      |                   double        int
      |                  %d
11_type_inference_1.c:9:24: warning: format ‘%f’ expects argument of type ‘double’, but argument 4 has type ‘int’ [-Wformat=]
    9 |     printf("%d x %f = %f\n", x, y, z);
      |                       ~^           ~
      |                        |           |
      |                        double      int
      |                       %d
gcc -Wall -std=c23 -pedantic -o 12_type_inference_2 12_type_inference_2.c
gcc -Wall -std=c23 -pedantic -o 13_check_type 13_check_type.c
gcc -Wall -std=c23 -pedantic -o 14_pointers 14_pointers.c
gcc -Wall -std=c23 -pedantic -o 15_typeof 15_typeof.c
gcc -Wall -std=c23 -pedantic -o 16_typeof_type 16_typeof_type.c
make: Target 'all' not remade because of errors.
Repositář s demonstračními příklady

Všechny demonstrační příklady, s nimiž jsme se v dnešním článku seznámili a které jsou určeny pro překlad s využitím gcc (lze ovšem použít i clang), jsou dostupné, jak se již stalo zvykem, na GitHubu:

Odkazy na Internetu
  1. Why C is Still Relevant in 2025: Use Cases in Modern Systems
    https://medium.com/@amypocreative/why-c-is-still-relevant-in-2025-use-cases-in-modern-systems-eafd12ab8afa
  2. Why Does C Still Matter in Modern Software Development
    https://codefinity.com/blog/Why-Does-C-Still-Matter-in-Modern-Software-Development
  3. Why C Programming Still Matters in the Era of High-Level Languages: The Enduring Relevance of C in AI and Concept Building
    https://www.linkedin.com/pulse/why-c-programming-still-matters-era-high-level-languages-datathick-jmbdc/
  4. Původní domovská stránka Tiny C Compileru
    https://bellard.org/tcc/
  5. Tiny C Compiler na Wikipedii
    https://en.wikipedia.org/wi­ki/Tiny_C_Compiler
  6. TinyCC Assembler
    https://bellard.org/tcc/tcc-doc.html#asm
  7. Repositář Tiny C Compileru
    https://repo.or.cz/w/tinycc.git
  8. When did people first start thinking ‚C is portable assembler‘?
    https://stackoverflow.com/qu­estions/3040276/when-did-people-first-start-thinking-c-is-portable-assembler
  9. The Thirty Million Line Problem
    https://www.youtube.com/wat­ch?v=kZRE7HIO3vk
  10. NesDev.org
    https://www.nesdev.org/
  11. How to Program an NES game in C
    https://nesdoug.com/
  12. Comparison of instruction set architectures
    https://en.wikipedia.org/wi­ki/Comparison_of_instructi­on_set_architectures
  13. Vývojové nástroje používané v dobách osmibitových mikropočítačů
    https://www.root.cz/clanky/vyvojove-nastroje-pouzivane-v-dobach-osmibitovych-mikropocitacu/
  14. C Isn't A Programming Language Anymore
    https://faultlore.com/blah/c-isnt-a-language/
  15. Why the C Language Will Never Stop You from Making Mistakes
    https://thephd.dev/your-c-compiler-and-standard-library-will-not-help-you
  16. Modern C Programming: Embracing New Features, Standards, and Historical Changes
    https://www.coderancher.us/2023/10/13/mo­dern-c-programming-embracing-new-features-standards-historical-changes/
  17. Strana mírného pokroku v mezích zákona
    https://cs.wikipedia.org/wi­ki/Strana_m%C3%ADrn%C3%A9ho_po­kroku_v_mez%C3%ADch_z%C3%A1ko­na
  18. Vznik Unixu a jazyka B na počítačích PDP
    https://www.root.cz/clanky/vznik-unixu-a-jazyka-b-na-pocitacich-pdp/
  19. Storage Classes in C
    https://www.geeksforgeeks­.org/c/storage-classes-in-c/
  20. Storage class (Wikipedia)
    https://en.wikipedia.org/wi­ki/C_syntax#Storage_class_spe­cifiers
  21. cc65 na GitHubu
    https://github.com/cc65/cc65
  22. cc65 (Dokumentace)
    https://cc65.github.io/
  23. cc65 (Wikipedia)
    https://en.wikipedia.org/wiki/Cc65
  24. Type inference
    https://en.wikipedia.org/wi­ki/Type_inference
  25. ML (Computer program language)
    https://www.barnesandnoble­.com/b/books/other-programming-languages/ml-computer-program-language/_/N-29Z8q8Zvy7
  26. Strong Typing
    https://perl.plover.com/y­ak/typing/notes.html
  27. What to know before debating type systems
    http://blogs.perl.org/user­s/ovid/2010/08/what-to-know-before-debating-type-systems.html
  28. Types, and Why You Should Care (Youtube)
    https://www.youtube.com/wat­ch?v=0arFPIQatCU
  29. The Development of the C Language
    http://cm.bell-labs.com/cm/cs/who/dmr/chist.html
  30. B (programming language)
    http://en.wikipedia.org/wi­ki/B_(programming_language)
  31. Users' Reference to Bo
    http://cm.bell-labs.com/cm/cs/who/dmr/kbman.html
  32. THE PROGRAMMING LANGUAGE B
    http://cm.bell-labs.com/cm/cs/who/dmr/bintro.html
  33. BCPL
    http://en.wikipedia.org/wiki/BCPL
  34. Ken Thompson
    http://en.wikipedia.org/wi­ki/Ken_Thompson_(computer_pro­grammer)
  35. Dennis Ritchie
    http://en.wikipedia.org/wi­ki/Dennis_Ritchie
  36. Brian Kernighan
    http://en.wikipedia.org/wi­ki/Brian_Kernighan
  37. Douglas McIlroy
    http://en.wikipedia.org/wi­ki/Douglas_McIlroy
  38. M. Douglas McIlroy, Adjunct Professor
    http://www.cs.dartmouth.edu/~doug/
  39. Programmed Data Processor
    http://en.wikipedia.org/wi­ki/Programmed_Data_Proces­sor
  40. Digital Equipment Corporation
    http://en.wikipedia.org/wi­ki/Digital_Equipment_Corpo­ration
  41. Ancient Computing Machinery
    http://www.ee.ryerson.ca/~elf/ancient-comp/index.html
  42. Programmed Data Processor-1 Handbook
    http://www.dbit.com/~green­g3/pdp1/pdp1.html

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Softwarová sklizeň (5. 8. 2026): bezpečnostní laboratoř za pár sekund017.9604-08-2026
2Что лучше — C++ или C#?07.529-06-2026
3Lupa: propojení programovacích jazyků Lua a Python08.2610-08-2026
4Заумство терминологий0529-03-2026
5Podpora unifikované práce se zařízeními v operačním systému Atari: CIO (dokončení)08.4312-08-2026
6Podpora unifikované práce se zařízeními v operačním systému Atari: CIO (2.část)07.4605-08-2026
7Программисты на Руси программируют на Си. RUSI: Концепт билингвального компилятора06.7404-08-2026
8АвтонОМмы, СоЕДИНяйТя Ся¡¡¡👍👏👋🤞🤟🤘🫰👌🙏😇💯🔥⚡💫💥🤗 10111-07-2026
9Сбербанк ускоряет ИТ-разработку с помощью новой функциональности облачной версии GigaCode CLI0520-07-2026

Классификация: . Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 6.85. Источник: www.root.cz.