Překladače na platformě IBM PC: od assembleru k C (32bitový kód a FPU)

27. 5. 2025
Doba čtení: 43 minut

Sdílet

Autor: Depositphotos
Na úvodní článek o vyšších jazycích používaných v době kralování IBM PC a DOSu dnes navážeme. Minule jsme se zaměřili na 16bitový kód, dnes si ukážeme ty překladače, které dokážou i pro DOS vytvářet 32bitový kód.

Obsah

1. Překladače na platformě IBM PC: od assembleru k C (32bitový kód)

2. Vyplnění pole zadanou hodnotou

3. Výsledky vygenerované překladačem Borland C++

4. Výsledky vygenerované překladačem Watcom C/C++

5. Porovnání s moderními instrukčními sadami a moderními překladači

6. Překladače podporující 32bitovou instrukční sadu

7. Šestnáctibitové a 32bitovové instrukce v reálném a chráněném režimu

8. DJGPP

9. Malá odbočka: RHIDE

10. Překlad testovacích příkladů pomocí DJGPP

11. Funkce pro součet dvou celých čísel

12. Funkce pro vyplnění paměťové oblasti

13. Funkce pro nalezení největší hodnoty v poli

14. Funkce pro vyplnění pole zadanou hodnotou

15. Podpora operací s plovoucí řádovou čárkou v céčkových překladačích

16. Funkce pro součet dvou hodnot typu float

17. Výsledek překladu pomocí Borland C++

18. Výsledek překladu pomocí Watcom C/C++ a DJGPP

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

20. Odkazy na Internetu

1. Překladače na platformě IBM PC: od assembleru k C (32bitový kód)

Na úvodní článek o vyšších programovacích jazycích používaných v době kralování platformy IBM PC a operačního systému DOSu dnes navážeme. Zatímco minule jsme se zaměřili na šestnáctibitový kód, jehož struktura musela odpovídat šestnáctibitovým mikroprocesorům původní řady 8086 (a také 80286), dnes (alespoň prozatím) přeskočíme jednu trnitou kapitolu vývoje (snahu o rozumné možnosti adresace celé dostupné paměti v reálném režimu) a zaměříme se na ty překladače jazyka C, které dokázaly generovat 32bitový kód kompatibilní s mikroprocesory 80386 a 80486. Ovšem nejenom to – tyto překladače obsahovaly podporu pro takzvané DOS extendery umožňující běh programů v chráněném režimu, ve kterém již neplatí omezení režimu reálného. Právě tyto překladače umožnili vývoj rozsáhlejších aplikací, samozřejmě včetně her.

Poznámka: navíc si ukážeme, jak dobře či naopak špatně dokázaly překladače programovacího jazyka C pracovat s matematickým koprocesorem, což byla další potenciálně problematická komponenta platformy IBM PC – minimálně v prvních deseti letech existence této platformy totiž bylo nutné počítat s tím, že počítače nejsou matematickým koprocesorem vůbec osazeny (a tudíž se nějakým způsobem musela řešit problematika emulace výpočtů atd.)

2. Vyplnění pole zadanou hodnotou

Ještě se ovšem alespoň na chvíli zastavme u překladu zdrojových kódů z jazyka C do šestnáctibitové instrukční sady kompatibilní s původními mikroprocesory Intel 8086 (resp. Intel 8088). Poměrně dlouhou dobu se tradovalo, že překladačům jazyka C je v mnoha případech nutné „dopomoci“ v případě, že se přistupuje k prvkům polí. Namísto přístupu k prvkům polí přes index bylo doporučováno přímé využití ukazatelů, což překladačům mělo umožnit zjednodušené výpočty adres (ještě před programovou smyčkou atd.). Ostatně jeden takový příklad jsme si ukazovali minule. Jednalo se o realizaci funkce memset, jejíž zdrojový kód používá adresaci přes ukazatel:

void * memset(void *dest, register int val, register size_t len) {
    register unsigned char *ptr = (unsigned char*)dest;
    while (len-- > 0)
        *ptr++ = val;
    return dest;
}

V dalších kapitolách se pokusíme zjistit, jak dobře či naopak špatně dokážou překladače optimalizovat funkci určenou pro vyplnění celého pole zadanou hodnotou. Tato činnost do značné míry odpovídá funkci memset zmíněné výše. Nyní ovšem budeme k prvkům polí přistupovat důsledně přes index, tj. bez využití ukazatelů:

void fill_array(int *array, int size, int value) {
    int i;
    for (i=0; i<size; i++) {
        array[i] = value;
    }
}
Poznámka: to znamená, že naivní způsob překladu by vedl k tomu, že se v každé iteraci bude znovu počítat adresa prvku s indexem i, tj. vlastně *(array+i).

3. Výsledky vygenerované překladačem Borland C++

Nejprve se podívejme na strojový kód vygenerovaný překladačem Borland C++ 2.0, s nímž jsme se již setkali minule. Bude se jednat o čistě šestnáctibitový kód, což znamená, že datový typ int bude mít šířku dvou bajtů, což mj. omezuje maximální velikost pole. To pochopitelně ovlivní způsob adresování prvků v poli, které je na šestnáctibitové architektuře z tohoto pohledu jednoduché:

_fill_array     proc    near
        push    bp
        mov     bp,sp
        push    si
   ;    
   ;        int i;
   ;        for (i=0; i<size; i++) {
   ;    
        xor     si,si
        jmp     short @1@98
@1@50:
   ;    
   ;            array[i] = value;
   ;    
        mov     ax,si
        shl     ax,1
        mov     bx,word ptr [bp+4]
        add     bx,ax
        mov     ax,word ptr [bp+8]
        mov     word ptr [bx],ax
        inc     si
@1@98:
        cmp     si,word ptr [bp+6]
        jl      short @1@50
   ;    
   ;        }
   ;    }
   ;    
        pop     si
        pop     bp
        ret
_fill_array     endp

Povšimněte si především toho, že překladač sice použil index registr SI pro úschovu adresy, ovšem jeho zvýšení o hodnotu 2 (16bitů) je řešeno přes akumulátor AX a vlastní přístup k prvku zajišťuje registr BX (vždy se do něj znovu načte bázová adresa pole). To vlastně znamená, že jedna operace je řešena s využitím tří registrů:

        mov     ax,si
        shl     ax,1
        mov     bx,word ptr [bp+4]
        add     bx,ax
        ...
        mov     word ptr [bx],ax
        inc     si

Dva přístupy do paměti jsou zbytečné. Zejména naplnění registru BX bázovou adresou pole by mohlo být vyřešeno mimo vlastní programovou smyčku – kód je mj. i v tomto ohledu dosti neoptimální. V tomto případě je tedy do určité míry pravda, že překladač přístup do pole neoptimalizuje (což jsme všem ani nevyžadovali).

Překladač Borland C++ dokáže provádět i malé optimalizace, což se projeví i na způsobu zápisu prvků do pole:

_fill_array     proc    near
        push    bp
        mov     bp,sp
        push    si
        push    di
        mov     di,word ptr [bp+4]
        mov     dx,word ptr [bp+6]
        mov     cx,word ptr [bp+8]
   ;    
   ;        int i;
   ;        for (i=0; i<size; i++) {
   ;    
        xor     si,si
        jmp     short @1@98
@1@50:
   ;    
   ;            array[i] = value;
   ;    
        mov     bx,si
        shl     bx,1
        mov     ax,cx
        mov     word ptr [bx+di],ax
        inc     si
@1@98:
        cmp     si,dx
        jl      short @1@50
   ;    
   ;        }
   ;    }
   ;    
        pop     di
        pop     si
        pop     bp
        ret
_fill_array     endp

V tomto případě je zápis do pole kratší. Pro adresaci je opět použit registr BX společně s bází uloženou v registru DI (změna oproti předchozímu postupu):

        mov     bx,si
        shl     bx,1
        mov     ax,cx
        mov     word ptr [bx+di],ax
        inc     si

4. Výsledky vygenerované překladačem Watcom C/C++

Druhým překladačem programovacího jazyka C, jehož možnosti otestujeme, je – podobně jako minule – Watcom C/C++. Ten by měl umět vygenerovat lepší (přesněji řečeno rychlejší) kód, než Borland C++. Podívejme se nejdříve na způsob překladu funkce fill_array při vypnutí všech optimalizací:

Module: C:\fill_a.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000022 bytes
0000                          fill_array_:
0000    50                        push        ax
0001    B8 06 00                  mov         ax,0x0006
0004    E8 00 00                  call        __STK
0007    58                        pop         ax
0008    51                        push        cx
0009    56                        push        si
000A    89 C6                     mov         si,ax
000C    89 D9                     mov         cx,bx
000E    31 C0                     xor         ax,ax
0010                          L$1:
0010    39 D0                     cmp         ax,dx
0012    7D 0B                     jge         L$2
0014    89 C3                     mov         bx,ax
0016    D1 E3                     shl         bx,0x01
0018    01 F3                     add         bx,si
001A    89 0F                     mov         word ptr [bx],cx
001C    40                        inc         ax
001D    EB F1                     jmp         L$1
001F                          L$2:
001F    5E                        pop         si
0020    59                        pop         cx
0021    C3                        ret
 
Routine Size: 34 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Instrukce realizující interní počítanou smyčku sice nejsou naprogramovány ideálně (dokážeme napsat lepší kód), ovšem provádí se zde pouze jediný přístup do operační paměti, což je oproti překladači Borland C++ lepší řešení. Stále se však pro adresaci prvků pole používá zbytečně velké množství pracovních registrů, což je patrné ve chvíli, kdy z výsledného kódu vykopírujeme pouze vnitřní programovou smyčku:

L$1:
    cmp         ax,dx
    jge         L$2
    mov         bx,ax
    shl         bx,0x01
    add         bx,si
    mov         word ptr [bx],cx
    inc         ax
    jmp         L$1

Následuje výpis kódu vytvořeného týmž překladačem, ovšem po zapnutí vybraných optimalizací. Celková délka kódu zůstává shodná, ovšem jednotlivé instrukce jsou v tomto případě odlišné:

Module: C:\fill_a.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000022 bytes
0000                          fill_array_:
0000    50                        push        ax
0001    B8 06 00                  mov         ax,0x0006
0004    E8 00 00                  call        __STK
0007    58                        pop         ax
0008    51                        push        cx
0009    56                        push        si
000A    89 C6                     mov         si,ax
000C    89 D9                     mov         cx,bx
000E    31 C0                     xor         ax,ax
0010                          L$1:
0010    39 D0                     cmp         ax,dx
0012    7D 0B                     jge         L$2
0014    89 C3                     mov         bx,ax
0016    01 C3                     add         bx,ax
0018    01 F3                     add         bx,si
001A    89 0F                     mov         word ptr [bx],cx
001C    40                        inc         ax
001D    EB F1                     jmp         L$1
001F                          L$2:
001F    5E                        pop         si
0020    59                        pop         cx
0021    C3                        ret
 
Routine Size: 34 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

V tomto případě se liší způsob výpočtu adresy prvku pole – pro tento účel se nyní používá pracovní registr BX, zatímco akumulátor AX vystupuje v roli počitadla smyčky a současně i registru pro uložení indexu. Bázová adresa pole se nachází v index registru SI a kupodivu se báze+offset sčítá explicitně a nikoli s využitím adresovacího režimu:

L$1:
    cmp         ax,dx
    jge         L$2
    mov         bx,ax
    add         bx,ax
    add         bx,si
    mov         word ptr [bx],cx
    inc         ax
    jmp         L$1

Zapnutí maximálních optimalizací vede k ještě zajímavějšímu kódu, jenž vypadá takto:

Module: C:\fill_a.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 0000001B bytes
0000                          fill_array_:
0000    51                        push        cx
0001    56                        push        si
0002    89 C6                     mov         si,ax
0004    89 D1                     mov         cx,dx
0006    89 DA                     mov         dx,bx
0008    89 F3                     mov         bx,si
000A    31 C0                     xor         ax,ax
000C                          L$1:
000C    39 C8                     cmp         ax,cx
000E    7D 08                     jge         L$2
0010    40                        inc         ax
0011    89 17                     mov         word ptr [bx],dx
0013    83 C3 02                  add         bx,0x0002
0016    EB F4                     jmp         L$1
0018                          L$2:
0018    5E                        pop         si
0019    59                        pop         cx
001A    C3                        ret
 
Routine Size: 27 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Vygenerovaný programový kód se o sedm bajtů zkrátil, ovšem důležité jsou optimalizace realizované ve vnitřní programové smyčce. Nyní je adresace skutečně krátká – využívá se zde pouze registr BX, jenž je původně nastaven na bázi pole (adresu prvního prvku). A i vlastní otestování, zda se dosáhlo konce pole, je realizováno porovnáním obsahu dvou registrů, což je velmi rychlá operace (první instrukce ve smyčce):

L$1:
    cmp         ax,cx
    jge         L$2
    inc         ax
    mov         word ptr [bx],dx
    add         bx,0x0002
    jmp         L$1

Pro zajímavost se ještě podívejme na způsob překladu též funkce, tentokrát ovšem do 32bitového kódu, ve kterém je šířka typu int rovna třiceti dvěma bitům, což ovlivňuje jak maximální délku pole, tak i rozsah hodnot jeho prvků:

Module: C:\fill_a.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE32 00000013 bytes
0000                          fill_array_:
0000    51                        push        ecx
0001    89 D1                     mov         ecx,edx
0003    31 D2                     xor         edx,edx
0005                          L$1:
0005    39 CA                     cmp         edx,ecx
0007    7D 08                     jge         L$2
0009    42                        inc         edx
000A    89 18                     mov         dword ptr [eax],ebx
000C    83 C0 04                  add         eax,0x00000004
000F    EB F4                     jmp         L$1
0011                          L$2:
0011    59                        pop         ecx
0012    C3                        ret
 
Routine Size: 19 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST DWORD USE32 00000000 bytes
git@github.com:tisnik/lightspeed-providers.git
Segment: CONST2 DWORD USE32 00000000 bytes
 
Segment: _DATA DWORD USE32 00000000 bytes

Šestnáctibitový a 32bitový kód se v tomto konkrétním případě liší vlastně jen nepatrně, což je ještě lépe patrné při porovnání stylem instrukce po instrukci:

L$1:                                  L$1:
    cmp         ax,cx                     cmp         edx,ecx
    jge         L$2                       jge         L$2
    inc         ax                        inc         edx
    mov         word ptr [bx],dx          mov         dword ptr [eax],ebx
    add         bx,0x0002                 add         eax,0x00000004
    jmp         L$1                       jmp         L$1
Poznámka: ve 32bitovém režimu je možné použít pro adresování i registr EAX, což je jeden z důvodů, proč je výsledný programový kód kratší, než jeho 16bitová varianta.

5. Porovnání s moderními instrukčními sadami a moderními překladači

A jak bude stejný zdrojový kód přeložen moderním překladačem s využitím moderní (minimálně dobou vzniku) instrukční sady x86–64? Výsledný kód je ovlivněn snahou o lepší využití fronty instrukcí, což vede k rozbalení smyčky atd. Například překladač GCC při použití základních optimalizací (konkrétně při použití přepínače -O2) vygeneruje kód, v němž se v každé iteraci do pole zapíše dvojice hodnot. Možnost, že by pole obsahovalo lichý počet prvků (což pochopitelně nelze vyloučit), je řešena ještě před vstupem do vlastní programové smyčky. Celý kód je kvůli těmto optimalizacím sice delší, ale díky částečnému rozbalení smyčky (od návěští .L3 do návěští .L1) se lépe využijí možnosti moderních CPU:

fill_array:
.LFB0:
        test    esi, esi
        jle     .L1
        movsx   rsi, esi
        lea     rax, [rdi+rsi*4]
        and     esi, 1
        je      .L3
        mov     DWORD PTR [rdi], edx
        add     rdi, 4
        cmp     rdi, rax
        je      .L11
.L3:
        mov     DWORD PTR [rdi], edx
        add     rdi, 8
        mov     DWORD PTR [rdi-4], edx
        cmp     rdi, rax
        jne     .L3
.L1:
        ret
.L11:
        ret

Mnohem delší a interně složitější kód je vygenerován při povolení maximální úrovně optimalizací (přepínač -O9). Ústředním prvkem výsledného kódu je v tomto případě částečně rozbalená programová smyčka pracující s registry XMMx, tedy se 128bitovými vektory. V jedné iteraci se do pole zapíše celých 256 bitů, tj. 32 bajtů (16+16):

.L4:
        movups  XMMWORD PTR [rax], xmm0
        add     rax, 32
        movups  XMMWORD PTR [rax-16], xmm0
        cmp     rax, rdi
        jne     .L4

Celý kód je ovšem ještě mnohem složitější, protože řeší přípravu konstanty uložené do registru XMM0 a taktéž stavy, kdy pole obsahuje počet prvků, který není dělitelný osmi:

fill_array:
.LFB0:
        mov     r8, rdi
        mov     ecx, esi
        test    esi, esi
        jle     .L1
        lea     eax, [rsi-1]
        cmp     eax, 2
        jbe     .L6
        shr     esi, 2
        movd    xmm1, edx
        mov     rax, rdi
        sal     rsi, 4
        pshufd  xmm0, xmm1, 0
        lea     rdi, [rsi+rdi]
        and     esi, 16
        je      .L4
        lea     rax, [r8+16]
        movups  XMMWORD PTR [r8], xmm0
        cmp     rax, rdi
        je      .L13
.L4:
        movups  XMMWORD PTR [rax], xmm0
        add     rax, 32
        movups  XMMWORD PTR [rax-16], xmm0
        cmp     rax, rdi
        jne     .L4
.L13:
        mov     eax, ecx
        and     eax, -4
        test    cl, 3
        je      .L15
.L3:
        movsx   rsi, eax
        mov     DWORD PTR [r8+rsi*4], edx
        lea     rdi, [0+rsi*4]
        lea     esi, [rax+1]
        cmp     ecx, esi
        jle     .L1
        add     eax, 2
        mov     DWORD PTR [r8+4+rdi], edx
        cmp     ecx, eax
        jle     .L1
        mov     DWORD PTR [r8+8+rdi], edx
.L1:
        ret
.L15:
        ret
.L6:
        xor     eax, eax
        jmp     .L3

Naopak, pokud si vyžádáme vygenerování co nejkratšího kódu přepínačem -Os, získáme velmi jednoduchou smyčku s adresováním typu báze+index*konstanta:

fill_array:
.LFB0:
        xor     eax, eax
.L2:
        cmp     esi, eax
        jle     .L5
        mov     DWORD PTR [rdi+rax*4], edx
        inc     rax
        jmp     .L2
.L5:
        ret
Poznámka: opět se zde využívá adresovací režim, který v původním šestnáctibitovém IBM PC vůbec nebyl k dispozici. To jen ukazuje postupný vývoj instrukčních sad – na jednu stranu směrem k RISCu, na stranu druhou se naopak instrukční sada a adresování stávají více ortogonální.

6. Překladače podporující 32bitovou instrukční sadu

Jednou z největších revolucí, která na platformě IBM PC proběhla, se stal postupný přechod od šestnáctibitových mikroprocesorů 8086 (resp. jejich varianty 8088) a 80286 k mikroprocesorům 80386. Interně se jednalo o zcela nové typy mikroprocesorů, které však nabízely plnou zpětnou kompatibilitu s předchozími čipy. Mikroprocesory 80386 nabízely nové užitečné instrukce, které jsme si již v tomto seriálu popsali (resp. alespoň většinu z nich).

To však není ani zdaleka vše, protože mikroprocesory 80386 a 80486 jsou nazývány 32bitovými čipy mj. i z toho důvodu, že se u nich rozšířila aritmeticko-logická jednotka takovým způsobem, aby bylo možné pracovat s 32bitovými operandy. A tomu se musela přizpůsobit i sada pracovních registrů. Společnost Intel se rozhodla, že namísto „párování“ dvou šestnáctibitových registrů do registrů 32bitových rozšíří původní registry ze šestnácti bitů na plných 32 bitů. Týká se to všech čtyř pracovních registrů AX, BX, CX, DX, indexových registrů SI, DI, bázových registrů SP, BP i ukazatele na instrukci IP. Tyto registry byly rozšířeny o horních šestnáct bitů, přičemž je stále možné adresovat spodních 16 bitů původními jmény registrů a/nebo (pouze u pracovních registrů) použít jejich horních a spodních osm bitů zvlášť (AX=AH+AL atd.). V assembleru se 32bitové registry poznají podle toho, že začínají písmenem E.

A konečně – zlepšily se možnosti adresování v chráněném režimu (což do jisté míry souvisí s možností pracovat s 32bitovými offsety) a navíc bylo možné využít i režim virtuální.

Všechny tyto změny museli tvůrci překladačů nějakým způsobem začít podporovat. V některých případech se jednalo „pouze“ o podporu nových instrukcí a rozšířených registrů, další překladače již dokázaly překládat plnohodnotný 32bitový kód pro chráněný režim. To je rozdíl, který se projevuje i na způsobu kódování instrukcí.

7. Šestnáctibitové a 32bitovové instrukce v reálném a chráněném režimu

V operačním systému DOS se ve výchozím stavu mikroprocesor nachází v takzvaném reálném režimu (real mode), ve kterém je sice možné používat jak šestnáctibitové, tak i 32bitové instrukce, ovšem v případě 32bitových instrukcí se před každou instrukci vloží prefix 0×66 nebo 0×67 (tj. instrukce jsou o jeden bajt delší). Ovšem naopak v 32bitovém chráněném režimu (protected mode) se prefixy vkládají před šestnáctibitové instrukce. Připomeňme si tyto rozdíly na dvojici krátkých příkladů psaných přímo v assembleru.

BITS 16         ; 16bitovy vystup pro DOS
CPU 386         ; specifikace pouziteho instrukcniho souboru
 
        xor  al, al
        inc  al
 
        xor  ax, ax
        inc  ax
 
        xor  eax, eax
        inc  eax

Po překladu si povšimněte prefixů 0×66 u posledních dvou instrukcí:

    27 00000000 30C0                            xor  al, al
    28 00000002 FEC0                            inc  al
    29
    30 00000004 31C0                            xor  ax, ax
    31 00000006 40                              inc  ax
    32
    33 00000007 6631C0                          xor  eax, eax
    34 0000000A 6640                            inc  eax

Mohlo by se zdát, že třetí generace mikroprocesorů sice dokáže provádět 32bitové operace, ale platíme za to dosti vysokou cenu – každá instrukce je o bajt delší, což ovlivňuje využití cache atd. Ve skutečnosti tomu tak je, jak jsme si již naznačili, pouze v původním šestnáctibitovém režimu mikroprocesorů. V případě, že se mikroprocesor nachází v režimu 32bitovém (čehož ovšem není zcela snadné dosáhnout, pokud si vše programujeme sami), bude situace prakticky opačná. Jak by situace vypadala v 32bitovém režimu, si můžeme ověřit překladem následujícího příkladu, v němž jsme pozměnili specifikaci BITS 16 na BITS 32. Výsledný binární soubor sice nebude v DOSu přímo spustitelný, ovšem nás nyní zajímá především způsob zakódování 32bitových instrukcí:

BITS 32         ; 16bitovy vystup pro DOS
CPU 386         ; specifikace pouziteho instrukcniho souboru
 
        xor  al, al
        inc  al
 
        xor  ax, ax
        inc  ax
 
        xor  eax, eax
        inc  eax

Nyní budou prefixy 0×66 použity naopak u šestnáctibitových operací. Operace nad bajty zůstanou v obou případech totožné:

    27 00000000 30C0                            xor  al, al
    28 00000002 FEC0                            inc  al
    29
    30 00000004 6631C0                          xor  ax, ax
    31 00000007 6640                            inc  ax
    32
    33 00000009 31C0                            xor  eax, eax
    34 0000000B 40                              inc  eax

Co to konkrétně znamená pro překladače jazyků C, Pascal atd.? Podívejme se nejprve na způsob překladu stejné funkce (memset), nejprve pro šestnáctibitový reálný režim. V prvním případě se používají jen šestnáctibitové instrukce, ovšem současně je velikost typu int pouze šestnáctibitová (což je mimochodem zcela v souladu s normami jazyka C!):

0000                          memset_:
0000    51                        push        cx
0001    89 C1                     mov         cx,ax
0003    89 D8                     mov         ax,bx
0005    89 CB                     mov         bx,cx
0007                          L$1:
0007    48                        dec         ax
0008    3D FF FF                  cmp         ax,0xffff
000B    74 05                     je          L$2
000D    88 17                     mov         byte ptr [bx],dl
000F    43                        inc         bx
0010    EB F5                     jmp         L$1
0012                          L$2:
0012    89 C8                     mov         ax,cx
0014    59                        pop         cx
0015    C3                        ret

Při překladu s využitím 32bitových instrukcí již bude velikost typu int taktéž 32bitová, ovšem současně překladač provedl překlad pro chráněný 32bitový režim. Za povšimnutí stojí prakticky stejná struktura výsledného kódu a taktéž adresace hodnotou uloženou v akumulátoru EAX (zatímco AX v režimu šestnáctibitovém pro tyto účely využít nelze):

0000                          memset_:
0000    51                        push        ecx
0001    89 C1                     mov         ecx,eax
0003                          L$1:
0003    4B                        dec         ebx
0004    83 FB FF                  cmp         ebx,0xffffffff
0007    74 05                     je          L$2
0009    88 10                     mov         byte ptr [eax],dl
000B    40                        inc         eax
000C    EB F5                     jmp         L$1
000E                          L$2:
000E    89 C8                     mov         eax,ecx
0010    59                        pop         ecx
0011    C3                        ret

Porovnání po jednotlivých instrukcích ukazuje, jak se vylepšené možnosti adresování v 32bitovém režimu projevily ve výsledném kódu:

memset_:                               memset_:
    push        cx                         push        ecx
    mov         cx,ax                      mov         ecx,eax
    mov         ax,bx
    mov         bx,cx
L$1:                                   L$1:
    dec         ax                         dec         ebx
    cmp         ax,0xffff                  cmp         ebx,0xffffffff
    je          L$2                        je          L$2
    mov         byte ptr [bx], dl          mov         byte ptr [eax],dl
    inc         bx                         inc         eax
    jmp         L$1                        jmp         L$1
L$2:                                   L$2:
    mov         ax,cx                      mov         eax,ecx
    pop         cx                         pop         ecx
    ret                                    ret
Poznámka: zajímavé je, že druhý kód je ve skutečnosti o čtyři bajty kratší, než kód první!

8. DJGPP

„DJGPP was born around 1989, when Richard Stallman spoke at … I asked if the FSF ever planned on porting gcc to MS-DOS, and he said it couldn't be done because gcc was too big and MS-DOS was a 16-bit operating system. Challenge in hand, I began.“

Naprostá většina překladačů vyšších programovacích jazyků, které byly pro platformu IBM PC a DOS dostupné v prvních deseti letech její existence, byla dostupná jako běžné komerční aplikace, které navíc nebyly vůbec levné. Dobrým příkladem je Lattice C, zpočátku ne moc dobrý překladač, který byl v roce 1982 (tedy v prvních letech existence platformy IBM PC) nabízen za 500 dolarů (tehdejších dolarů). Později se sice cena snížila, na což měla vliv společnost Borland, která nabízela relativně levné produkty, ovšem stále se nejednalo o zanedbatelnou položku. Později vznikly některé volně dostupné projekty popř. překladače šířené formou shareware (nízká cena) a freeware. Nicméně skutečný obrat nastal až s příchodem DJGPP, což bylo (a vlastně doposud je – projekt je totiž stále udržován) jméno pro balíček obsahující sadu GNU nástrojů, a to včetně GNU assembleru (gas), překladače jazyka C (gcc), GNU linkeru (ld), ale například i GNU debuggeru (gdb).

DJGGP a Borland C++ IDE

Obrázek 1: Instalace DJGPP v operačním systému DOS.

„The first gcc I built was 1.35, which I built on an ISC Unix system running on a 386/16. I wrote custom replacements for the system calls, linked with ISC's libc.a, write a custom program to turn the resulting binary into a 32-bit EXE that Phar Lap's extender could use, and had the first gcc that ran on MS-DOS.“

Navíc bylo nutné vyřešit způsob spouštění těchto nástrojů na operačním systému DOS s notoricky známými omezeními, které se týkají způsobu práce s operační pamětí. To bylo vyřešeno vyvinutím nového extenderu nazvaného CWSDPMI. Už jen fakt, že DJGPP podporoval vývoj aplikací, které tento extender podporovaly, stačil k rozšíření DJGPP a tím pádem i GCC mezi vývojáře. V DJGPP tak začalo vznikat poměrně velké množství aplikací pro operační systém DOS (nacházíme se v období, v němž se sice přecházelo na Windows, ale mnohé aplikace stále běžely v DOSu. Typickým příkladem jsou pochopitelně hry).

DJGGP a Borland C++ IDE

Obrázek 2: Překladač jazyka C GCC je volitelnou součástí DJGPP.

9. Malá odbočka: RHIDE

V souvislosti s projektem DJGPP je nutné se alespoň v krátkosti zmínit o integrovaném vývojovém prostředí RHIDE, které obsahovalo programátorský editor, správce projektů, rozhraní pro debugger atd. Celé prostředí bylo koncipováno takovým způsobem, aby připomínalo integrovaná vývojová prostředí nabízená společnosti Borland, takže přechod vývojářů na DJGPP+RHIDE byl (po prvotní konfiguraci, která určité znalosti vyžadovala) poměrně bezbolestný a rychlý (stejné či podobné klávesové zkratky, shodná okna, podobné dialogy). Vzhledem k tomu, že tento článek se zabývá překladači a nikoli IDE, ukážeme si pouze několik screenshotů RHIDE:

DJGGP a Borland C++ IDE

Obrázek 3: Z roku uvedeného v tomto dialogu je zřejmé, že RHIDE bylo vyvíjeno i poté, co už DOS prakticky přestal být (snad kromě embedded systémů) jen historickým termínem.

DJGGP a Borland C++ IDE

Obrázek 4: Programátorský editor se zvýrazněním syntaxe.

DJGGP a Borland C++ IDE

Obrázek 5: Průběh překladu projektu.

Poznámka: v DJGPP+RHIDE jsem napsal bakalářskou práci, jediným problémem byl pomalý překlad na počítači s procesorem 80486 (v porovnání s překladači Borlandu).

10. Překlad testovacích příkladů pomocí DJGPP

Testovací příklady, které jsme si ukázali v úvodním článku i v článku dnešním, se nyní pokusíme přeložit s využitím DJGPP v DOSu. Překlad bude vždy proveden několika způsoby – typicky bez povolených optimalizací, s povolenými optimalizacemi a většinou taktéž s optimalizacemi, které by měly vést ke kratšímu strojovému kódu. Výhodou projektu DJGPP je v této oblasti fakt, že se vlastně používá standardní překladač GCC, takže i jeho přepínače řídicí způsob překladu jsou stejné. A mimochodem – tyto stovky přepínačů lze změnit i v RHIDE v nastavení projektu:

DJGGP a Borland C++ IDE

Obrázek 6: Nastavení přepínačů překladače, které ovlivňují překlad.

11. Funkce pro součet dvou celých čísel

Začneme triviální funkcí, která provede součet dvou celých čísel a vrátí výsledek součtu. V DJGPP je typ int pochopitelně plně 32bitový:

int add(int a, int b) {
    return a+b;
}

Výsledek překladu, pokud jsou optimalizace zakázány. Vidíme zde standardní práci se zásobníkovým rámcem (instrukce PUSH+MOV na začátku a POP na konci). Výsledek je vrácen přes registr EAX:

_add:
LFB0:
        push    ebp
        mov     ebp, esp
        mov     edx, DWORD PTR [ebp+8]
        mov     eax, DWORD PTR [ebp+12]
        add     eax, edx
        pop     ebp
        ret

Výsledek překladu při povolení maximálních optimalizací. Nyní se již zásobníkový rámec neobsluhuje, nicméně se stále používají dva pracovní registry, i když by bylo možné kód ještě dále zjednodušit:

_add:
LFB0:
        mov     eax, DWORD PTR [esp+8]
        mov     edx, DWORD PTR [esp+4]
        add     eax, edx
        ret

Výsledek překladu při snaze o vytvoření nejkratšího kódu. Zajímavé je, že v tomto případě se sice skutečně celý výpočet provádí pouze s registrem EAX, ovšem současně se vytváří a ruší zásobníkový rámec.

_add:
LFB0:
        push    ebp
        mov     ebp, esp
        mov     eax, DWORD PTR [ebp+12]
        add     eax, DWORD PTR [ebp+8]
        pop     ebp
        ret

Nejkratší kód a volba -fomit-frame-pointer, která zabrání vytvoření standardního zásobníkového rámce:

_add:
LFB0:
        mov     eax, DWORD PTR [ebp+8]
        add     eax, DWORD PTR [ebp+4]
        ret

12. Funkce pro vyplnění paměťové oblasti

Další funkcí, jejíž překlad pomocí DJGPP si ověříme, je funkce určená pro vyplnění paměťové oblasti určené ukazatelem. I tuto funkci jsme si ukázali minule, takže jen krátce:

#include <mem.h>
 
void * memset(void *dest, register int val, register size_t len) {
    register unsigned char *ptr = (unsigned char*)dest;
    while (len-- > 0)
        *ptr++ = val;
    return dest;
}

Překlad se zakázanými optimalizacemi, ve kterém kromě jiného můžeme vidět dvojí přístup do paměti, i to, že zápis je prováděn po bajtech:

_memset:
LFB0:
        push    ebp
        mov     ebp, esp
        push    ebx
        mov     ecx, DWORD PTR [ebp+16]
        mov     ebx, DWORD PTR [ebp+8]
        jmp     L2
L3:
        mov     eax, ebx
        lea     ebx, [eax+1]
        mov     dl, BYTE PTR [ebp+12]
        mov     BYTE PTR [eax], dl
L2:
        mov     eax, ecx
        lea     ecx, [eax-1]
        test    eax, eax
        jne     L3
        mov     eax, DWORD PTR [ebp+8]
        pop     ebx
        pop     ebp
        ret

Překlad s povolenými maximálními optimalizacemi vede k tomu, že zápis do pole je prováděn po čtyřech prvcích, ale pochopitelně se zajištěním funkcionality v případě, že délka pole není dělitelná čtyřmi:

_memset:
LFB0:
        push    edi
        push    ebx
        mov     edx, DWORD PTR [esp+20]
        test    edx, edx
        je      L12
        xor     ebx, ebx
        mov     edi, DWORD PTR [esp+12]
        mov     bl, BYTE PTR [esp+16]
        cmp     edx, 4
        jnb     L22
L3:
        and     edx, 3
        je      L12
        xor     eax, eax
L6:
        mov     BYTE PTR [edi+eax], bl
        inc     eax
        cmp     eax, edx
        jb      L6
L12:
        mov     eax, DWORD PTR [esp+12]
        pop     ebx
        pop     edi
        ret
L22:
        xor     eax, eax
        mov     al, bl
        mov     ah, al
        mov     ecx, eax
        sal     ecx, 16
        or      eax, ecx
        test    edi, 1
        jne     L23
L4:
        test    edi, 2
        jne     L24
L5:
        mov     ecx, edx
        shr     ecx, 2
        rep stosd
        jmp     L3
L23:
        mov     BYTE PTR [edi], al
        dec     edx
        mov     ecx, DWORD PTR [esp+12]
        lea     edi, [ecx+1]
        jmp     L4
L24:
        mov     WORD PTR [edi], ax
        sub     edx, 2
        add     edi, 2
        jmp     L5

A konečně výsledek snahy o krátký výsledný strojový kód. Zápis je prováděn pomalu – po bajtech a dokonce se v každé iteraci provádí jedno čtení a jeden zápis do paměti:

_memset:
LFB0:
        push    ebp
        mov     ebp, esp
        push    ebx
        mov     edx, DWORD PTR [ebp+8]
        mov     ecx, DWORD PTR [ebp+16]
        add     ecx, edx
        mov     eax, edx
L2:
        cmp     eax, ecx
        je      L6
        inc     eax
        mov     bl, BYTE PTR [ebp+12]
        mov     BYTE PTR [eax-1], bl
        jmp     L2
L6:
        pop     ebx
        mov     eax, edx
        pop     ebp
        ret

13. Funkce pro nalezení největší hodnoty v poli

Zajímavější a interně složitější je funkce, která má nalézt největší prvek v poli s prvky typu unsigned int. Tato funkce (i s jejím ověřením) vypadá následovně:

#include <stdio.h>
 
typedef unsigned int uint;
 
uint find_max(uint *array, uint length) {
    uint max = 0;
    uint i;
    uint *item = array;
 
    for (i=0; i<length; i++) {
        if (max < *item) {
            max = *item;
        }
        item++;
    }
    return max;
}
 
int main(void) {
#define LENGTH 10
 
    uint array[LENGTH] = {5, 6, 7, 8, 9, 0, 1, 2, 3, 4};
    uint max = find_max(array, LENGTH);
    printf("%d\n", max);
    return 0;
}

Výsledek překladu po zákazu optimalizací. Povšimněte si, že nyní se používají „lokální proměnné“ uložené na zásobníkovém rámci (veškeré přístupy do paměti s EBP-hodnota):

_find_max:
LFB0:
        push    ebp
        mov     ebp, esp
        sub     esp, 16
        mov     DWORD PTR [ebp-4], 0
        mov     eax, DWORD PTR [ebp+8]
        mov     DWORD PTR [ebp-12], eax
        mov     DWORD PTR [ebp-8], 0
        jmp     L2
L4:
        mov     eax, DWORD PTR [ebp-12]
        mov     eax, DWORD PTR [eax]
        cmp     DWORD PTR [ebp-4], eax
        jnb     L3
        mov     eax, DWORD PTR [ebp-12]
        mov     eax, DWORD PTR [eax]
        mov     DWORD PTR [ebp-4], eax
L3:
        add     DWORD PTR [ebp-12], 4
        inc     DWORD PTR [ebp-8]
L2:
        mov     eax, DWORD PTR [ebp-8]
        cmp     eax, DWORD PTR [ebp+12]
        jb      L4
        mov     eax, DWORD PTR [ebp-4]
        leave
        ret

Po povolení optimalizací se již žádné lokální proměnné nejsou zapotřebí a výpočty se provádí s hodnotami uloženými v registrech nebo přímo v poli. Povšimněte si, že ze subrutiny se vyskakuje na dvou místech:

_find_max:
LFB0:
        push    esi
        push    ebx
        mov     ebx, DWORD PTR [esp+16]
        mov     esi, DWORD PTR [esp+12]
        test    ebx, ebx
        je      L5
        xor     eax, eax
        xor     ecx, ecx
L4:
        mov     edx, DWORD PTR [esi+eax*4]
        cmp     ecx, edx
        jnb     L3
        mov     ecx, edx
L3:
        inc     eax
        cmp     ebx, eax
        jne     L4
        pop     ebx
        mov     eax, ecx
        pop     esi
        ret
L5:
        xor     ecx, ecx
        pop     ebx
        mov     eax, ecx
        pop     esi
        ret

Nejčitelnější obecně bývá výsledek překladu při snaze o vytvoření nejkratšího kódu. Je tomu tak i v tomto případě, ovšem nyní se uvnitř smyčky provádí hned tři přístupy do operační paměti, což pochopitelně nevede k nejrychlejšímu kódu:

_find_max:
LFB0:
        push    ebp
        xor     eax, eax
        mov     ebp, esp
        xor     edx, edx
L2:
        cmp     eax, DWORD PTR [ebp+12]
        je      L7
        mov     ecx, DWORD PTR [ebp+8]
        mov     ecx, DWORD PTR [ecx+eax*4]
        cmp     edx, ecx
        jnb     L3
        mov     edx, ecx
L3:
        inc     eax
        jmp     L2
L7:
        mov     eax, edx
        pop     ebp
        ret

14. Funkce pro vyplnění pole zadanou hodnotou

Třetí příklad, jehož překlad pomocí DJGPP si otestujeme, obsahuje funkci pro vyplnění pole zadanou hodnotou. Tedy jedná se o příklad ze druhé kapitoly:

void fill_array(int *array, int size, int value) {
    int i;
    for (i=0; i<size; i++) {
        array[i] = value;
    }
}

Opět se nejdříve podíváme na výsledek překladu se zákazem optimalizací. Ten vypadá následovně. Je zde patrné hned několik přístupů do paměti (zásobníku atd.) uvnitř smyčky:

_fill_array:
LFB0:
        push    ebp
        mov     ebp, esp
        sub     esp, 16
        mov     DWORD PTR [ebp-4], 0
        jmp     L2
L3:
        mov     eax, DWORD PTR [ebp-4]
        lea     edx, [0+eax*4]
        mov     eax, DWORD PTR [ebp+8]
        add     edx, eax
        mov     eax, DWORD PTR [ebp+16]
        mov     DWORD PTR [edx], eax
        inc     DWORD PTR [ebp-4]
L2:
        mov     eax, DWORD PTR [ebp-4]
        cmp     eax, DWORD PTR [ebp+12]
        jl      L3
        nop
        nop
        leave
        ret

Mnohem efektivnější je kód získaný po povolení všech optimalizací na rychlost. Nyní je programová smyčka zkrácena na pouhé čtyři instrukce s jediným přístupem do paměti:

_fill_array:
LFB0:
        mov     ecx, DWORD PTR [esp+8]
        mov     edx, DWORD PTR [esp+12]
        test    ecx, ecx
        jle     L1
        mov     eax, DWORD PTR [esp+4]
        lea     ecx, [eax+ecx*4]
L3:
        mov     DWORD PTR [eax], edx
        add     eax, 4
        cmp     ecx, eax
        jne     L3
L1:
        ret

Výsledek překladu při snaze o co nejmenší kód, která ovšem tvoří standardní zásobníkové rámce:

_fill_array:
LFB0:
        push    ebp
        xor     eax, eax
        mov     ebp, esp
        mov     edx, DWORD PTR [ebp+8]
        mov     ecx, DWORD PTR [ebp+16]
L2:
        cmp     eax, DWORD PTR [ebp+12]
        jge     L6
        mov     DWORD PTR [edx+eax*4], ecx
        inc     eax
        jmp     L2
L6:
        pop     ebp
        ret

Snaha o co nejkratší kód se zákazem vytváření standardních zásobníkových rámců:

_fill_array:
LFB0:
        mov     edx, DWORD PTR [esp+4]
        mov     ecx, DWORD PTR [esp+12]
        xor     eax, eax
L2:
        cmp     eax, DWORD PTR [esp+8]
        jge     L5
        mov     DWORD PTR [edx+eax*4], ecx
        inc     eax
        jmp     L2
L5:
        ret

15. Podpora operací s plovoucí řádovou čárkou v céčkových překladačích

Překladače programovacího jazyka C se na platformě IBM PC (a DOS) musely vypořádat ještě s jedním problémem – jakým způsobem zajistit výpočty s hodnotami s plovoucí řádovou čárkou, tj. v céčku s hodnotami typu float a double. Vzhledem k tomu, že již víme, že matematický koprocesor byl volitelnou součástí počítače (což se změnilo až v dobách Pentia), mohlo nastat několik variant:

  1. Program byl přeložen takovým způsobem, že matematický koprocesor striktně vyžadoval
  2. Program byl naopak přeložen tak, že všechny výpočty vždy prováděl softwarově (a tudíž velmi pomalu)
  3. Na začátku byla provedena detekce matematického koprocesoru a výpočet se realizoval dvakrát (byl proveden rozeskok)
  4. Využívalo se systému přerušení, kdy knihovna pro softwarové výpočty reagovala na situaci, kdy došlo k přerušení při pokusu o vyvolání FPU operace na počítači, který koprocesor neobsahoval
DJGGP a Borland C++ IDE

Obrázek 7: Volby v IDE překladače Borland C++, které ovlivňují, jakým způsobem se budou provádět výpočty s hodnotami s plovoucí řádovou čárkou.

16. Funkce pro součet dvou hodnot typu float

Abychom si otestovali, jakým způsobem překladače dokážou provádět výpočty s FPU hodnotami, necháme si přeložit tuto jednoduchou funkci, která má sečíst své dva parametry typu float a vrátit výsledek součtu:

float add(float x, float y) {
    return x+y;
}

17. Výsledek překladu pomocí Borland C++

Překladač Borland C++ dokáže generovat instrukce matematického koprocesoru a v případě potřeby je dokáže i emulovat. Výsledek překladu výše uvedené funkce je poměrně přímočarý. Připomeňme si jen, že parametry jsou předány přes zásobník a po vytvoření zásobníkového rámce (BP) jsou umístěny na adresách BP+4 a BP+8:

   ;    
   ;    float add(float x, float y) {
   ;    
        assume  cs:_TEXT
_add    proc    near
        push    bp
        mov     bp,sp
   ;    
   ;        return x+y;
   ;    
        fld     dword ptr [bp+4]
        fadd    dword ptr [bp+8]
        jmp     short @1@50
@1@50:
   ;    
   ;    }
   ;    
        pop     bp
        ret
_add    endp

Jediná dostupná optimalizace odstraní krátký skok na další adresu, takže strojový kód výsledné subrutiny bude vypadat následovně:

_TEXT   segment byte public 'CODE'
   ;    
   ;    float add(float x, float y) {
   ;    
        assume  cs:_TEXT
_add    proc    near
        push    bp
        mov     bp,sp
   ;    
   ;        return x+y;
   ;    
        fld     dword ptr [bp+4]
        fadd    dword ptr [bp+8]
   ;    
   ;    }
   ;    
        pop     bp
        ret
_add    endp

18. Výsledek překladu pomocí Watcom C/C++ a DJGPP

Překladač Watcom C/C++ pochopitelně taktéž podporuje práci s matematickým koprocesorem. Překlad bez optimalizací vypadá prakticky stejně, jak u překladače Borland C++, ovšem povšimněte si explicitního volání instrukce FWAIT (lze vypnout):

Module: c:\add_f.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000017 bytes
0000                          add_:
0000    B8 04 00                  mov         ax,0x0004
0003    E8 00 00                  call        __STK
0006    55                        push        bp
0007    89 E5                     mov         bp,sp
0009    9B D9 46 04               fld         dword ptr 0x4[bp]
000D    9B D8 46 08               fadd        dword ptr 0x8[bp]
0011    90                        nop
0012    9B                        fwait
0013    5D                        pop         bp
0014    C2 08 00                  ret         0x0008
 
Routine Size: 23 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Po provedení optimalizací se subrutina zmenší na sedmnáct bajtů, ovšem základ je stále zachován:

Module: c:\add_f.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000011 bytes
0000                          add_:
0000    55                        push        bp
0001    89 E5                     mov         bp,sp
0003    9B D9 46 04               fld         dword ptr 0x4[bp]
0007    9B D8 46 08               fadd        dword ptr 0x8[bp]
000B    90                        nop
000C    9B                        fwait
000D    5D                        pop         bp
000E    C2 08 00                  ret         0x0008
 
Routine Size: 17 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

A konečně můžeme povolit využití FPU knihovny. Poté je překlad triviální – provede se jen skok na subrutiny __FSA, což pravděpodobně znamená floating single add:

Module: c:\add_f.c
GROUP: 'DGROUP' CONST,CONST2,_DATA
 
Segment: _TEXT BYTE USE16 00000003 bytes
0000                          add_:
0000    E9 00 00                  jmp         __FSA
 
Routine Size: 3 bytes,    Routine Base: _TEXT + 0000
 
No disassembly errors
 
Segment: CONST WORD USE16 00000000 bytes
 
Segment: CONST2 WORD USE16 00000000 bytes
 
Segment: _DATA WORD USE16 00000000 bytes

Poslední je DJGPP. Překlad se zákazem optimalizací dopadne takto:

_add:
        push    ebp
        mov     ebp, esp
        fld     DWORD PTR [ebp+8]
        fadd    DWORD PTR [ebp+12]
        pop     ebp
        ret

Po povolení optimalizací se pouze prohodí operandy součtu (kdoví proč):

_add:
        push    ebp
        mov     ebp, esp
        fld     DWORD PTR [ebp+12]
        fadd    DWORD PTR [ebp+8]
        pop     ebp
        ret

Výsledek překladu při povolení optimalizací a zákazu použití standardních zásobníkových rámců:

_add:
        fld     DWORD PTR [esp+8]
        fadd    DWORD PTR [esp+4]
        ret

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

Demonstrační příklady napsané v jazyce C, které jsou primárně určené pro překlad s využitím překladačů Turbo C a (Open)Watcom C), byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

Borland C:

# Příklad Stručný popis Adresa
1 add.c funkce pro součet dvou celých čísel naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add.c
2 add1.asm překlad přes assembler, externí symboly začínají pomlčkou https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add1.asm
3 add2.asm překlad přes assembler, externí symboly nejsou přejmenovány https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add2.asm
4 add3.asm výsledek překladu se zákazem optimalizací (externí symboly s pomlčkou) https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add3.asm
5 add4.asm výsledek překladu se zákazem optimalizací (externí symboly nejsou přejmenovány) https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add4.asm
6 add5.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add5.asm
7 memset.c funkce pro vyplnění paměťového bloku naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/memset.c
8 memset1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/memset1.asm
9 memset2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/memset2.asm
10 find_max.c funkce pro nalezení největšího prvku v poli naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max.c
11 find_max1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max1.asm
12 find_max2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max2.asm
13 find_max3.asm výsledek překladu s povolením instrukcí procesorů 80286 https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/find_max3.asm
       
14 fill_a.c funkce pro vyplnění pole konstantou naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/fill_a.c
15 fill_a1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/fill_a1.asm
16 fill_a2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/fill_a2.asm
       
17 add_f.c funkce pro součet dvou čísel s plovoucí řádovou čárkou naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/bc/add_f.c
18 add_f1.asm překlad přes assembler https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/add_f1.asm
19 add_f2.asm překlad přes assembler s eliminací skoků a dalšími optimalizacemi https://github.com/tisnik/8bit-fame/blob/master/compiler­s/bc/add_f2.asm

(Open)Watcom pro platformu IBM PC:

# Příklad Stručný popis Adresa
1 add.c funkce pro součet dvou celých čísel naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add.c
2 add1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add1.asm
3 add2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add2.asm
4 memset.c funkce pro vyplnění paměťového bloku naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset.c
5 memset1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset1.asm
6 memset2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset2.asm
7 memset3.asm výsledek překladu do 32bitového kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/memset3.asm
8 find_max.c funkce pro nalezení největšího prvku v poli naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max.c
9 find_max1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max1.asm
10 find_max2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max2.asm
11 find_max3.asm výsledek překladu do 32bitového kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/find_max3.asm
       
12 fill_a.c funkce pro vyplnění pole konstantou naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/fill_a.c
13 fill_a1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/fill_a1.asm
14 fill_a2.asm výsledek překladu s povolením některých optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/fill_a2.asm
15 fill_a3.asm výsledek překladu s povolením všech optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/fill_a3.asm
16 fill_a4.asm výsledek překladu do 32bitového kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/fill_a4.asm
       
17 add_f.c funkce pro součet dvou čísel s plovoucí řádovou čárkou naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add_f.c
18 add_f1.asm překlad přes assembler https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add_f1.asm
19 add_f2.asm překlad přes assembler s optimalizacemi https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add_f2.asm
20 add_f3.asm překlad přes assembler s využitím FP knihovny https://github.com/tisnik/8bit-fame/blob/master/compiler­s/watcom/add_f3.asm

GCC pro platformu x86–64:

# Příklad Stručný popis Adresa
1 add.c funkce pro součet dvou celých čísel naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compilers/gcc/add.c
2 add.asm výsledek překladu do assembleru s použitím optimalizací https://github.com/tisnik/8bit-fame/blob/master/compilers/gcc/add.asm
3 memset.c funkce pro vyplnění paměťového bloku naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/memset.c
4 memset1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/memset1.asm
5 memset2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/memset2.asm
6 find_max.c funkce pro nalezení největšího prvku v poli naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max.c
7 find_max_default.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max_default.asm
8 find_max_smallest.asm výsledek překladu s optimalizací na velikost kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max_smallest.asm
9 find_max_vector.asm výsledek překladu s optimalizací na rychlost https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/find_max_vector.asm
       
10 fill_a.c funkce pro vyplnění pole konstantou naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/fill_a.c
11 fill_a_default.s výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/fill_a_default.s
12 fill_a_O2.s výsledek překladu s přepínačem -O2 https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/fill_a_O2.s
13 fill_a_O9.s výsledek překladu s přepínačem -O9 https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/fill_a_O9.s
14 fill_a_smallest.s výsledek překladu při snaze o vytvoření nejkratšího kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/gcc/fill_a_smallest.s

DGJPP pro platformu IBM PC + DOS:

# Příklad Stručný popis Adresa
1 add.c funkce pro součet dvou celých čísel naprogramovaná v C https://github.com/tisnik/8bit-fame/blob/master/compilers/djgpp/add.c
2 add1.asm výsledek překladu do assembleru se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add1.asm
3 add2.asm výsledek překladu do assembleru s povolením optimalizací na rychlost https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add2.asm
4 add3.asm výsledek překladu do assembleru s povolením optimalizací na velikost kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add3.asm
5 add4.asm explicitní specifikace, že se nemusí vytvářet standardní zásobníkové rámce https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add4.asm
       
6 memset.c funkce pro vyplnění paměťového bloku naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/memset.c
7 memset1.asm výsledek překladu do assembleru se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/memset1.asm
8 memset2.asm výsledek překladu do assembleru s povolením optimalizací na rychlost https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/memset2.asm
9 memset3.asm výsledek překladu do assembleru s povolením optimalizací na velikost kódu https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/memset3.asm
       
10 find_max.c funkce pro nalezení největšího prvku v poli naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/find_max.c
11 findmax1.asm překlad bez povolení optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/findmax1.asm
12 findmax2.asm překlad s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/findmax2.asm
13 findmax3.asm překlad s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/findmax3.asm
       
14 fill_a.c funkce pro vyplnění pole konstantou naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/fill_a.c
15 fill_a1.asm překlad bez povolení optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/fill_a1.asm
16 fill_a2.asm překlad s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/fill_a2.asm
17 fill_a3.asm optimalizace na velikost https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/fill_a3.asm
18 fill_a4.asm nejkratší možný kód https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/fill_a4.asm
       
19 add_f.c funkce pro součet dvou čísel s plovoucí řádovou čárkou naprogramovaná v jazyku C https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add_f.c
20 add_f1.asm výsledek překladu se zákazem optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add_f1.asm
21 add_f2.asm výsledek překladu s povolením optimalizací https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add_f2.asm
22 add_f3.asm výsledek překladu s povolením volání FPU knihovny https://github.com/tisnik/8bit-fame/blob/master/compiler­s/djgpp/add_f3.asm

20. Odkazy na Internetu

  1. DJGPP (Wikipedia)
    https://cs.wikipedia.org/wiki/DJGPP
  2. DJGPP home page
    http://www.delorie.com/djgpp/
  3. DJGPP Zip File Picker
    http://www.delorie.com/djgpp/zip-picker.html
  4. The Intel 8088 Architecture and Instruction Set
    https://people.ece.ubc.ca/~ed­c/464/lectures/lec4.pdf
  5. x86 Opcode Structure and Instruction Overview
    https://pnx.tf/files/x86_op­code_structure_and_instruc­tion_overview.pdf
  6. x86 instruction listings (Wikipedia)
    https://en.wikipedia.org/wi­ki/X86_instruction_listin­gs
  7. x86 assembly language (Wikipedia)
    https://en.wikipedia.org/wi­ki/X86_assembly_language
  8. Intel Assembler (Cheat sheet)
    http://www.jegerlehner.ch/in­tel/IntelCodeTable.pdf
  9. 25 Microchips That Shook the World
    https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world
  10. Chip Hall of Fame: MOS Technology 6502 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor
  11. Chip Hall of Fame: Intel 8088 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-intel-8088-microprocessor
  12. Jak se zrodil procesor?
    https://www.root.cz/clanky/jak-se-zrodil-procesor/
  13. Apple II History Home
    http://apple2history.org/
  14. The 8086/8088 Primer
    https://www.stevemorse.or­g/8086/index.html
  15. flat assembler: Assembly language resources
    https://flatassembler.net/
  16. FASM na Wikipedii
    https://en.wikipedia.org/wiki/FASM
  17. Fresh IDE FASM inside
    https://fresh.flatassembler.net/
  18. MS-DOS Version 4.0 Programmer's Reference
    https://www.pcjs.org/docu­ments/books/mspl13/msdos/dos­ref40/
  19. DOS API (Wikipedia)
    https://en.wikipedia.org/wiki/DOS_API
  20. Bit banging
    https://en.wikipedia.org/wi­ki/Bit_banging
  21. IBM Basic assembly language and successors (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Basic_assembly_lan­guage_and_successors
  22. X86 Assembly/Bootloaders
    https://en.wikibooks.org/wi­ki/X86_Assembly/Bootloaders
  23. Počátky grafiky na PC: grafické karty CGA a Hercules
    https://www.root.cz/clanky/pocatky-grafiky-na-pc-graficke-karty-cga-a-hercules/
  24. Co mají společného Commodore PET/4000, BBC Micro, Amstrad CPC i grafické karty MDA, CGA a Hercules?
    https://www.root.cz/clanky/co-maji-spolecneho-commodore-pet-4000-bbc-micro-amstrad-cpc-i-graficke-karty-mda-cga-a-hercules/
  25. Karta EGA: první použitelná barevná grafika na PC
    https://www.root.cz/clanky/karta-ega-prvni-pouzitelna-barevna-grafika-na-pc/
  26. RGB Classic Games
    https://www.classicdosgames.com/
  27. Turbo Assembler (Wikipedia)
    https://en.wikipedia.org/wi­ki/Turbo_Assembler
  28. Microsoft Macro Assembler
    https://en.wikipedia.org/wi­ki/Microsoft_Macro_Assembler
  29. IBM Personal Computer (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Personal_Computer
  30. Intel 8251
    https://en.wikipedia.org/wi­ki/Intel_8251
  31. Intel 8253
    https://en.wikipedia.org/wi­ki/Intel_8253
  32. Intel 8255
    https://en.wikipedia.org/wi­ki/Intel_8255
  33. Intel 8257
    https://en.wikipedia.org/wi­ki/Intel_8257
  34. Intel 8259
    https://en.wikipedia.org/wi­ki/Intel_8259
  35. Support/peripheral/other chips – 6800 family
    http://www.cpu-world.com/Support/6800.html
  36. Motorola 6845
    http://en.wikipedia.org/wi­ki/Motorola_6845
  37. The 6845 Cathode Ray Tube Controller (CRTC)
    http://www.tinyvga.com/6845
  38. CRTC operation
    http://www.6502.org/users/an­dre/hwinfo/crtc/crtc.html
  39. The 6845 Cathode Ray Tube Controller (CRTC)
    http://www.tinyvga.com/6845
  40. Motorola 6845 and bitwise graphics
    https://retrocomputing.stac­kexchange.com/questions/10996/mo­torola-6845-and-bitwise-graphics
  41. IBM Monochrome Display Adapter
    http://en.wikipedia.org/wi­ki/Monochrome_Display_Adap­ter
  42. Color Graphics Adapter
    http://en.wikipedia.org/wi­ki/Color_Graphics_Adapter
  43. Color Graphics Adapter and the Brown color in IBM 5153 Color Display
    https://www.aceinnova.com/en/e­lectronics/cga-and-the-brown-color-in-ibm-5153-color-display/
  44. The Modern Retrocomputer: An Arduino Driven 6845 CRT Controller
    https://hackaday.com/2017/05/14/the-modern-retrocomputer-an-arduino-driven-6845-crt-controller/
  45. flat assembler: Assembly language resources
    https://flatassembler.net/
  46. FASM na Wikipedii
    https://en.wikipedia.org/wiki/FASM
  47. Fresh IDE FASM inside
    https://fresh.flatassembler.net/
  48. MS-DOS Version 4.0 Programmer's Reference
    https://www.pcjs.org/docu­ments/books/mspl13/msdos/dos­ref40/
  49. DOS API (Wikipedia)
    https://en.wikipedia.org/wiki/DOS_API
  50. IBM Basic assembly language and successors (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Basic_assembly_lan­guage_and_successors
  51. X86 Assembly/Arithmetic
    https://en.wikibooks.org/wi­ki/X86_Assembly/Arithmetic
  52. Art of Assembly – Arithmetic Instructions
    http://oopweb.com/Assembly/Do­cuments/ArtOfAssembly/Volu­me/Chapter6/CH06–2.html
  53. ASM Flags
    http://www.cavestory.org/gu­ides/csasm/guide/asm_flag­s.html
  54. Status Register
    https://en.wikipedia.org/wi­ki/Status_register
  55. Is it worthwhile to learn x86 assembly language today?
    https://www.quora.com/Is-it-worthwhile-to-learn-x86-assembly-language-today?share=1
  56. Why Learn Assembly Language?
    http://www.codeproject.com/Ar­ticles/89460/Why-Learn-Assembly-Language
  57. Is Assembly still relevant?
    http://programmers.stackex­change.com/questions/95836/is-assembly-still-relevant
  58. Why Learning Assembly Language Is Still a Good Idea
    http://www.onlamp.com/pub/a/on­lamp/2004/05/06/writegreat­code.html
  59. Assembly language today
    http://beust.com/weblog/2004/06/23/as­sembly-language-today/
  60. Assembler: Význam assembleru dnes
    http://www.builder.cz/rubri­ky/assembler/vyznam-assembleru-dnes-155960cz
  61. Programming from the Ground Up Book – Summary
    http://savannah.nongnu.or­g/projects/pgubook/
  62. DOSBox
    https://www.dosbox.com/
  63. The C Programming Language
    https://en.wikipedia.org/wi­ki/The_C_Programming_Langu­age
  64. Hercules Graphics Card (HCG)
    https://en.wikipedia.org/wi­ki/Hercules_Graphics_Card
  65. Complete 8086 instruction set
    https://content.ctcd.edu/cou­rses/cosc2325/m22/docs/emu8086in­s.pdf
  66. Complete 8086 instruction set
    https://yassinebridi.github.io/asm-docs/8086_instruction_set.html
  67. 8088 MPH by Hornet + CRTC + DESiRE (final version)
    https://www.youtube.com/wat­ch?v=hNRO7lno_DM
  68. Area 5150 by CRTC & Hornet (Party Version) / IBM PC+CGA Demo, Hardware Capture
    https://www.youtube.com/wat­ch?v=fWDxdoRTZPc
  69. 80×86 Integer Instruction Set Timings (8088 – Pentium)
    http://aturing.umcs.maine­.edu/~meadow/courses/cos335/80×86-Integer-Instruction-Set-Clocks.pdf
  70. Colour Graphics Adapter: Notes
    https://www.seasip.info/Vin­tagePC/cga.html
  71. Restoring A Vintage CGA Card With Homebrew HASL
    https://hackaday.com/2024/06/12/res­toring-a-vintage-cga-card-with-homebrew-hasl/
  72. Demoing An 8088
    https://hackaday.com/2015/04/10/de­moing-an-8088/
  73. Warnings Are Your Friend – A Code Quality Primer
    https://hackaday.com/2018/11/06/war­nings-are-your-friend-a-code-quality-primer/
  74. Defending Against Compiler-Based Backdoors
    https://blog.regehr.org/archives/1241
  75. Reflections on Trusting Trust
    https://www.win.tue.nl/~a­eb/linux/hh/thompson/trus­t.html
  76. Coding Machines (povídka)
    https://www.teamten.com/law­rence/writings/coding-machines/
  77. Stage0
    https://bootstrapping.mira­heze.org/wiki/Stage0
  78. Projekt stage0 na GitHubu
    https://github.com/oriansj/stage0
  79. Bootstraping wiki
    https://bootstrapping.mira­heze.org/wiki/Main_Page
  80. Bootstrapped 6502 Assembler
    https://github.com/robinluc­key/bootstrap-6502
  81. IBM Basic assembly language and successors (Wikipedia)
    https://en.wikipedia.org/wi­ki/IBM_Basic_assembly_lan­guage_and_successors
  82. X86 Assembly/Bootloaders
    https://en.wikibooks.org/wi­ki/X86_Assembly/Bootloaders
  83. What is a coder's worst nightmare?
    https://www.quora.com/What-is-a-coders-worst-nightmare/answer/Mick-Stute
  84. Tiny C Compiler
    https://bellard.org/tcc/
  85. Welcome to C–
    https://www.cs.tufts.edu/~nr/c--/index.html
  86. c4 – C in four functions
    https://github.com/rswier/c4
  87. Tiobe index
    https://www.tiobe.com/tiobe-index/
  88. Lattice C (Wikipedia)
    https://en.wikipedia.org/wi­ki/Lattice_C
  89. Aztec C (Wikipedia)
    https://en.wikipedia.org/wiki/Aztec_C
  90. Digital Mars (Wikipedia)
    https://en.wikipedia.org/wi­ki/Digital_Mars
  91. Stránky projektu Open Watcom
    https://openwatcom.org/
  92. Repositář Open Watcom
    https://github.com/open-watcom/open-watcom-v2
  93. Watcom C/C++ (Wikipedia)
    https://en.wikipedia.org/wi­ki/Watcom_C/C%2B%2B
  94. Turbo C (Wikipedia)
    https://en.wikipedia.org/wiki/Turbo_C
  95. Borland C++ (Wikipedia)
    https://en.wikipedia.org/wi­ki/Borland_C%2B%2B
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.