XVI.  

JEDNOTKA  SYSTEM


Jednotka System implementuje základní prostředky jazyka Turbo Pascal — standardní konstanty, typy, proměnné a podprogramy, mechanismus ošetření běhových chyb, správu heapu a další. Je automaticky užita všemi pascalskými moduly (programy i programovými jednotkami), aniž by bylo nutné uvádět její identifikátor v klauzulích uses. Všechny pascalské moduly jsou tudíž na jednotce System závislé a v každém z nich tvoří System hierarchicky nejvyšší blok.

Většina prostředků jednotky System byla již postupně probrána v předchozích kapitolách. Zde jsou popsány jen prostředky dosud neuvedené.

XVI.1. 

Ovládání diskových souborů

Kromě prostředků, popsaných v kapitole 9, lze pro správu diskových souborů využít následující procedury a funkce jednotky System. Jejich chybové stavy rovněž podléhají standardnímu mechanismu zpracování — při nastavené direktivě {$I–} je lze indikovat funkcí IOResult a ošetřit uživatelsky (viz Ošetření chybových stavů).

procedure ChDir (Cesta: string);

Nastaví pracovní adresář (obdoba příkazu chdir operačního systému DOS). V parametru Cesta je očekávána systémová specifikace zvoleného adresáře (smí obsahovat i specifikaci diskové jednotky).

procedure GetDir (Disk: Byte; var Cesta: string);

V parametru Cesta vrací úplnou systémovou specifikaci pracovního adresáře disku, jehož číslo je zadáno v parametru Disk (0 = aktuální disk, 1 = A, 2 = B, 3 = C, ...).

procedure MkDir (Cesta: string);

Vytvoří nový diskový adresář (obdoba systémového příkazu mkdir). Parametr Cesta je systémová specifikace zakládaného adresáře.

procedure RmDir (Cesta: string);

Odstraní z disku adresář, jehož specifikace je uvedena v parametru Cesta (obdoba systémového příkazu rmdir). Mazaný adresář musí být prázdný.

procedure Rename (var F: soubor; NoveJmeno: string);

Přejmenuje fyzický diskový soubor nebo adresář, spojený s parametrem F (obdoba systémového příkazu rename). Skutečným parametrem F smí být proměnná libovolného typu soubor, již spojená s existujícím fyzickým souborem nebo adresářem, soubor však nesmí být otevřený. Specifikace nového jména je očekávána v parametru NoveJmeno a smí umístit soubor do jiného adresáře, avšak nikoliv na jiný disk.

procedure Erase (var F: soubor);

Vymaže fyzický diskový soubor, spojený s parametrem F (obdoba systémového příkazu erase). Skutečným parametrem F smí být proměnná libovolného typu soubor, již spojená s existujícím fyzickým souborem, ne však s adresářem. Soubor nesmí být otevřený.

XVI.2. 

Generátor náhodných čísel

V některých aplikacích se vyskytuje požadavek na generování náhodných hodnot. K tomuto účelu implementuje System následující prostředky:

function Random: Real;
function Random (Rozsah: Word): Word;

Funkce Random vrací jistým způsobem zvolené (vypočtené) pseudonáhodné číslo, které lze použít pro výběr náhodné hodnoty požadovaného typu. Obor hodnot funkce závisí na parametru Rozsah. Při volání bez parametru je funkcí vráceno reálné číslo z polootevřeného intervalu <0, 1). Pokud naopak parametr uveden je, funkce vrací celé číslo typu Word, a to z intervalu <0, Rozsah) pro nenulový parametr resp. nulu pro parametr nulový.

const RandSeed: LongInt = 0;

Výběr pseudonáhodného čísla, vraceného funkcí Random, je řízen aktuální hodnotou proměnné RandSeed. K dané hodnotě proměnné RandSeed funkce vždy vrátí totéž pevně dané náhodné číslo a následně aktualizuje hodnotu proměnné RandSeed pevně danou hodnotou následující. Pro danou počáteční hodnotu proměnné RandSeed (implicitně nula) je tedy generována vždy stejná posloupnost pevně daných pseudonáhodných čísel. Je-li třeba, aby aplikační program pracoval pokaždé s jinou posloupností pseudonáhodných čísel, je nutné inicializovat proměnnou RandSeed nějakou „náhodnou“ hodnotou, tj. takovou, která nebude při každém spuštění programu stejná. K tomu lze použít například uživatelem zadané náhodné číslo nebo proceduru Randomize.

procedure Randomize;

Do proměnné RandSeed dosadí hodnotu, odvozenou z aktuálního stavu systémových hodin počítače.


POZNÁMKA: V zásadě by bylo možné odvodit každé generované pseudonáhodné číslo rovnou z aktuálního stavu systémových hodin. Používaný algoritmus však zajišťuje rovnoměrné rozložení generovaných čísel na uvažovaném intervalu (všechna potenciálně možná čísla jsou generována se zhruba stejnou pravděpodobností).


PŘÍKLAD: Použití generátoru náhodných čísel demonstrují následující funkce. Funkce RandomRealRandomInteger poskytují pseudonáhodná čísla příslušného typu ze zadaného intervalu <MinMax). Funkce RandomString vrací pseudonáhodné řetězce znaků. Délka řetězce je přitom generována jako pseudonáhodné celé číslo z intervalu <0, 256) a ordinální čísla jednotlivých znaků řetězce jako pseudonáhodná celá čísla z intervalu <Ord ('A'), Ord ('Z') + 1) (verzálky anglické abecedy):

{----------------------------------------------------------------------}
function RandomReal (Min, Max: Real): Real;
{----------------------------------------------------------------------}
begin { RandomReal }
 RandomReal := Min + (Max - Min) * Random
end; { RandomReal }

{----------------------------------------------------------------------}
function RandomInteger (Min, Max: Integer): Integer;
{----------------------------------------------------------------------}
begin { RandomInteger }
 if Min < Max then RandomInteger := Min + Random (Max - Min)
              else RandomInteger := Min - Random (Min - Max)
end; { RandomInteger }

{----------------------------------------------------------------------}
function RandomString: string;
{----------------------------------------------------------------------}

 var S: string; I: Byte;

begin { RandomString }
 S := '';
 for I := 1 to Random (256) do
  S := S + Chr (Ord ('A') + Random (Ord ('Z') - Ord ('A') + 1));
 RandomString := S
end; { RandomString }

XVI.3. 

Parametry programu

Programům, spouštěným z operačního systému DOS, lze na příkazové řádce předávat parametry. Parametry jsou řetězce znaků, oddělené navzájem a od volacího řetězce programu (například c:\xyz\program.exe) jednou nebo více mezerami či tabulátory, které tudíž nemohou být jejich součástí. Mezi volacím řetězcem programu a prvním parametrem nemusí být oddělovač, pokud je prvním znakem parametru lomítko (/).

volací-řetězec-programu   parametr1   parametr2...
Operační systém ukládá obsah příkazové řádky do zvláštního úseku operační paměti, která je součástí pracovního prostředí programu. Program jej pak může kdykoli získat pomocí funkcí, implementovaných jednotkou System:

function ParamCount: Word;

Vrací počet parametrů, uvedených na příkazové řádce za volacím řetězcem programu.

function ParamStr (Index: Integer): string;

Vrací řetězec znaků, odpovídající Index-tému parametru programu. Řádné parametry jsou indexovány od jedné výše, pro Index rovný nule je funkcí vrácen volací řetězec programu, a pro Index záporný nebo větší než skutečný počet parametrů vrací funkce prázdný řetězec.


POZNÁMKA: Při ladění programu ve vývojovém prostředí Turbo Pascalu lze případné parametry zadat v dialogu, který otevře příkaz Parameters v nabídce Run. Pokud je pak přeložený kód programu umístěn pouze v operační paměti (tedy nikoliv v souboru na disku), vrací funkce ParamStr (0) volací řetězec vývojového prostředí.


PŘÍKLAD: Program RenMov přejmenuje zadaný diskový soubor (a případně jej přesune do jiného adresáře) pomocí standardní procedury Rename. Aktuální a novou specifikaci souboru převezme jako parametry z příkazové řádky.

{======================================================================}
program RenMov;
{======================================================================}

 var F     : file;                          { zpracovávaný soubor      }
     Chyba : Word;                          { kód případné IO chyby    }

begin { program }
 if ParamCount <> 2 then                    { chybný počet parametrů   }
  begin                                     { tisk nápovědy            }
   WriteLn('--------------------------------------------');
   WriteLn('RENMOV - přejmenování souboru nebo adresáře.');
   WriteLn('syntax :              renmov jméno novéjméno');
   WriteLn('--------------------------------------------')
  end
 else                                       { správný počet parametrů  }
  begin
   Assign(F, ParamStr(1));                  { spojení F se souborem,   }
                                            { který má být přejmenován }
   {$I-}                                    { vlastní ošetření IO chyb }
   Rename(F, ParamStr(2));                  { pokus o přejmenování     }
   Chyba := IOResult;                       { převzetí kódu chyby      }
   case Chyba of                            { chybový test             }
    0  : ;                                  { vše v pořádku            }
    2  : WriteLn('Soubor nenalezen!');      { chybová hlášení          }
    3  : WriteLn('Cesta nenalezena!');
    5  : WriteLn('Soubor ', ParamStr(2), ' již existuje!');
    17 : WriteLn('Soubor nelze přenést na jiný disk!');
    else WriteLn('Chyba č.', Chyba, '!')
   end
  end
end. { program }

XVI.4. 

Řízení průchodu cyklem

Průchod tělem kteréhokoliv z cyklů while, repeatfor lze předčasně ukončit voláním procedury Break resp. Continue.

procedure Break;

Procedura Break ukončí provádění cyklu, tj. provede skok za konec těla cyklu. Nezávisle na hodnotě podmínky resp. řídící proměnné cyklu pokračuje výpočet programu až prvním příkazem za tělem cyklu.

procedure Continue;

Procedura Continue ukončí aktuální průchod cyklem, tj. provede skok na konec těla cyklu (vynechá příkazy, uvedené mezi jejím voláním a koncem těla cyklu). Podle hodnoty podmínky resp. řídící proměnné cyklu se pak pokračuje dalším průchodem cyklu nebo až prvním příkazem za jeho tělem.

Volání procedur BreakContinue může být ve zdrojovém textu uvedeno pouze přímo uvnitř těla cyklu, jinak ohlásí překladač syntaktickou chybu. Nelze například umístit příkaz Break do těla podprogramu, který bude z těla cyklu volán.


POZNÁMKA: Proti neodůvodněnému používání procedur BreakContinue lze vznést stejné námitky, jako proti používání příkazu skoku. Zvláště při jejich aplikaci v rozsáhlých tělech cyklů přehlednost zdrojového textu rapidně klesá.

XVI.5. 

Předčasné ukončení výpočtu

Příkazem volání některé z následujících procedur jednotky System lze vynutit předčasné ukončení výpočtu aktuálního bloku resp. celého programu.

procedure Exit;

Ukončí výpočet aktuálně aktivního bloku. Výpočet programu pak pokračuje dál, jako by výpočet opuštěného bloku byl ukončen normálním způsobem. Volání procedury Exit se používá především pro předčasné ukončení výpočtu podprogramů, lze jej však umístit i do příkazové části programu (ukončen je pak výpočet celého programu).

procedure Halt;
procedure Halt (Kod: Word);

Volání procedury Halt ukončí běh programu (nezávisle na tom, v kterého bloku je umístěno). Nepovinný parametr Kod definuje výstupní kód programu, který bude předán operačnímu systému (implicitní hodnotou je nula). Výstupní kódy postupně volaných programů mohou být využity například pro větvení dávkových souborů, operační systém totiž poskytuje výstupní kód naposled ukončeného programu jako tzv. ErrorLevel. Výstupní kód nemusí být nutně kódem nějakého chybového stavu programu, může nést informaci o dosaženém výsledku jeho činnosti (viz níže uvedený příklad).

procedure RunError;
procedure RunError (Kod: Word);

Volání procedury RunError má stejný efekt jako volání procedury Halt a navíc generuje běhovou chybu programu (výpis chybového hlášení).


PŘÍKLAD: Program Compare testuje ekvivalenci obsahu dvou souborů, jejichž systémové specifikace převezme z příkazové řádky. Výsledek poskytne formou výstupního kódu.

{======================================================================}
program Compare;
{======================================================================}

 const BufSize    = 64000;                      { velikost bufferů     }
 type  Buf        = array [1..BufSize] of Byte; { struktura bufferů    }
 var   Buf1, Buf2 : ^Buf;                       { ukazatele na buffery }
       F1, F2     : file;                       { testované soubory    }
       I          : Word;                       { pomocná proměnná     }

 {$I-}                                { vlastní ošetření IO chyb       }

 procedure Quit (Err: Byte);          { úklid a ukončení běhu programu }
 {-------------------------}          { s nastavením ERRORLEVEL na Err }
 begin {Quit}
  Close(F1);                          { zavření souborů                }
  Close(F2);
  Release(HeapOrg);                   { uvolnění alokované paměti      }
  Halt(Err)
 end;  {Quit}

begin { program }
 if ParamCount <> 2 then              { chybný počet parametrů         }
  Halt(3);                            { ERRORLEVEL = 3                 }
 Assign(F1, ParamStr(1));             { přidělení specifikací souborů  }
 Assign(F2, ParamStr(2));             { z příkazové řádky              }
 FileMode := 0;                       { otevření souborů F1 a F2       }
 Reset(F1, 1);                        { pouze pro čtení                }
 Reset(F2, 1);
 if IOResult <> 0 then                { chyba při otvírání souborů     }
  Quit(4);                            { ERRORLEVEL = 4                 }
 {$I+}                                { automatické ošetření IO chyb   }
 if FileSize(F1) <> FileSize(F2) then { různé velikosti souborů        }
  Quit(1);                            { ERRORLEVEL = 1                 }
 if MaxAvail < BufSize then           { nedostatek paměti              }
  Quit(5);                            { ERRORLEVEL = 5                 }
 New(Buf1);                           { vytvoření Buf1^ na heapu       }
 if MaxAvail < BufSize then           { nedostatek paměti              }
  Quit(5);                            { ERRORLEVEL = 5                 }
 New(Buf2);                           { vytvoření Buf2^ na heapu       }
 while not EOF(F1) do                 { dokud není konec souborů       }
  begin
   BlockRead(F1, Buf1^, BufSize, I);  { načtení bufferů                }
   BlockRead(F2, Buf2^, BufSize, I);
   for I := 1 to I do                 { pro všechny načtené byty       }
    if Buf1^[I] <> Buf2^[I] then      { test nerovnosti                }
     Quit(2)                          { ERRORLEVEL = 2                 }
  end;
                                      { všechny odpovídající si byty   }
                                      { souborů jsou totožné           }
 Quit(0)                              { ERRORLEVEL = 0                 }
end.  {program}

Po překladu programu Compare na disk do souboru compare.exe lze použít následující dávkový soubor co.bat pro jeho otestování. Spouští se příkazem co s1 s2, kde parametry s1s2 jsou specifikace porovnávaných souborů.

@echo off
echo.
compare.exe %1 %2

if ErrorLevel 5 goto Memory
if ErrorLevel 4 goto Reset
if ErrorLevel 3 goto Parametry
if ErrorLevel 2 goto Ruzne
if ErrorLevel 1 goto Delka

:Totozne
 echo Totožné soubory!
 goto Konec

:Memory
 echo Nedostatek paměti!
 goto Konec

:Reset
 echo Chyba při otvírání souborů!
 goto Konec

:Parametry
 echo Chybný počet parametrů!
 echo syntax : co soubor1 soubor2
 goto Konec

:Delka
 echo Zjištěny rozdílné velikosti souborů.

:Ruzne
 echo Různé soubory!

:Konec

XVI.6. 

Instalace úklidových procedur

Možných příčin korektního ukončení běhu programu je celkem pět:

Ve všech těchto případech program před předáním řízení operačnímu systému ještě volá svou úklidovou proceduru (pokud je ovšem nainstalována). Úklidová procedura umožňuje programu získat kontrolu nad procesem jeho ukončení. Její činnost spočívá zpravidla v úklidových a záchranných pracích, které musí být nutně před ukončením programu provedeny (uložení dat, zavření použitých souborů, obnova modifikovaných přerušovacích vektorů, vypnutí tónového generátoru a pod.). Ukončovací procedura může také potlačit standardní chybová hlášení a nahradit je vlastními nebo modifikovat výstupní kód programu.


POZNÁMKA: Při běhu programu, spuštěného z vývojového prostředí Turbo Pascalu, způsobí povel CTRL+BREAK pouze dočasné přerušení výpočtu programu a návrat do vývojového prostředí (odkud pak lze pokračovat například jeho krokováním) — úklidová procedura v tomto případě vykonána není.

Instalace ukončovací procedury spočívá ve vložení adresy jejího vstupního bodu do proměnné ExitProc, deklarované jednotkou System.

const ExitProc: Pointer = nil;

Pokud je při ukončování běhu programu hodnota proměnné ExitProc různá od nil, je použita jako adresa volané procedury. Aby mohly instalovat vlastní úklidové procedury i programem použité jednotky, probíhá proces postupného odvolávání úklidových procedur v cyklu: dokud je ExitProc <> nil, volej proceduru na adrese ExitProc. Každá z volaných procedur pak musí do proměnné ExitProc vložit adresu případné další úklidové procedury.

Před spuštěním každé z úklidových procedur je hodnota proměnné ExitProc automaticky nastavena na nil. Tím je vyloučen vznik nekonečného cyklu, k němuž by jinak došlo při výskytu běhové chyby v těle úklidové procedury. Aby se pro tento případ vyloučilo i nebezpečí přerušení postupného odvolávání úklidových procedur dalších modulů, musí být aktualizace hodnoty proměnné ExitProc prvním příkazem těla každé z úklidových procedur.

Samotná úklidová procedura podléhá stejným syntaktickým pravidlům, jako kterákoliv jiná procedura. Nesmí však mít žádné parametry a musí být zkompilována pro daleký model volání. V okamžiku její kompilace však není známo, že se jedná o úklidovou proceduru, proto na dodržení těchto požadavků nemůže dohlédnout kompilátor, takže nejpravděpodobnějším důsledkem jejich porušení je havárie systému.

Následující kostra demonstruje korektní instalaci úklidové procedury — s uložením a pozdějším obnovením hodnoty proměnné ExitProc:

 ...                           { deklarační část programu nebo         }
                               { implementační část jednotky           }
 var NextExitProc: Pointer;    { proměnná pro úschovu původní hodnoty  }
 ...                           { proměnné ExitProc                     }
 procedure Konec; far;         { úklidová procedura modulu             }
 {-------------------}
 begin { Konec }
  ExitProc := NextExitProc;    { obnova původní hodnoty ExitProc       }
  ...                          { vlastní činnost úklidové procedury    }
 end; { Konec }
 ...
begin                          { příkazová část programu nebo jednotky }
 NextExitProc := ExitProc;     { úschova původní hodnoty ExitProc      }
 ExitProc     := @Konec;       { instalace úklidové procedury Konec    }
 ...

Příčinu ukončení programu lze uvnitř úklidové procedury rozeznat podle hodnoty proměnných ErrorAddrExitCode jednotky System.

const ErrorAddr: Pointer = nil;

Je-li příčinou ukončení programu běhová chyba (samovolná nebo generovaná procedurou RunError), obsahuje ErrorAddr adresu instrukce v kódu programu, při jejímž provádění k chybě došlo. V ostatních případech má ErrorAddr hodnotu nil. Dosazením nil do proměnné ErrorAddr může úklidová procedura potlačit výpis standardního chybového hlášení.

const ExitCode: Integer = 0;

Obsahuje číslo, které bude předáno operačnímu systému jako výstupní kód programu a případně i použito jako číslo běhové chyby ve výpisu chybového hlášení (pokud je ErrorAddr = nil). Implicitní nula je nahrazena při výskytu běhové chyby jejím číslem, při volání procedury Halt pak hodnotou uvedeného parametru. Při ukončení programu povelem CTRL+BREAK z klávesnice má ExitCode hodnotu 255.

Při spuštění programu jsou úklidové procedury jednotlivých jeho modulů instalovány v pořadí provádění jejich příkazových částí, úklidová procedura hlavního modulu programu je tedy instalována jako poslední. V procesu ukončení programu jsou pak úklidové procedury v důsledku popsaného způsobu instalace odvolávány v přesně opačném pořadí. Po odvolání všech úklidových procedur (po dosažení stavu ExitProc = nil) je vykonána ještě ukončovací sekvence jednotky System, která zajistí zavření souborů InputOutput, výpis případného chybového hlášení (při ErrorAddr <> nil), předání výstupního kódu programu (hodnoty proměnné ExitCode) a návrat do operačního systému.

XVI.7. 

Ostatní procedury a funkce

Ze zbývajících prostředků jednotky System uvádím již pouze následující procedury a funkce. Neuvedené prostředky buď vybočují z plánovaného rozsahu přednášek nebo nejsou příliš užitečné.

procedure FillChar (var Cil; Delka: Word; Vypln);

Hodnotou parametru Vypln vyplní úsek operační paměti o délce Delka bytů, počínaje prvním bytem proměnné Cil. Parametr Cil je proměnná libovolného typu (z hlediska FillChar je zajímavá pouze její adresa), Delka je výraz je výraz typu WordVypln je výraz ordinálního typu o velikosti jednoho bytu (Byte, Char apod.).


POZNÁMKA: Při vypnuté kontrole rozsahu (direktiva {$R–}) sice výraz Vypln může být libovolného ordinálního typu — jeho velikost pak není kontrolována — avšak z jeho výsledné hodnoty je použit pouze nejnižší byte.

procedure Move (var Zdroj, Cil; Delka: Word);

Zkopíruje obsah úseku operační paměti o délce Delka bytů, počínaje prvním bytem proměnné Zdroj, do úseku s počátečním bytem na adrese proměnné Cil. Případné překrytí zdrojové a cílové oblasti nemá na výsledek přenosu vliv. Parametry ZdrojCil jsou libovolné proměnné a Delka je výraz typu Word.

function SizeOf (Argument): Word;

Velmi užitečná funkce, která vrací paměťovou velikost parametru Argument v bytech. Parametrem může být libovolná proměnná nebo identifikátor typu. Vrácená hodnota je typu Word.

function Hi (Cislo: Word): Byte;
function Lo (Cislo: Word): Byte;

Funkce Hi resp. Lo vrací hodnotu vyššího resp. nižšího bytu parametru Cislo.

function Swap (Cislo: Word): Word;

Vrací hodnotu typu Word, získanou záměnou vyššího a nižšího bytu parametru Cislo.


POZNÁMKA: Při předávání hodnoty parametru Cislo funkcím Hi, LoSwap není prováděna kontrola rozsahu (ani při nastavené direktivě {$R+}), takže rozsah přípustných hodnot skutečného parametru odpovídá rozsahu typu LongInt. Předány jsou však pouze nejnižší dva byty parametru.