Dnes v rychlosti probereme locale a zpracování jednotlivých znaků, v druhé části článku dojde na standardní i nestandardní ukončování programu.
15.12.2005 06:00 | Jan Němec | czytane 23065×
RELATED ARTICLES
KOMENTARZE
Národní prostředí
Jazyk C pochází ze 70. let a je to na něm vidět. Před 35 lety jen málo
lidí od počítačů napadlo, že by někdo mohl chtít používat jinou znakovou sadu,
formát čísel, data a podobně, než používají Američané. Později byla do C jakási
podpora národního prostředí doplněna, ale na některých překladačích dosud
nefunguje nebo funguje jen částečně (což ale není problém gcc), takže pro
použití v přenositelném kódu je jen obtížně použitelná. Standardní knihovna
podporuje pouze terminálový
vstup a výstup, přičemž řetězce jsou jen pole bytů, která se na výstup
posílají v podobě binárně shodné se zápisem ve zdrojovém kódu. Lokalizace
tak má jen omezené pole působnosti, neboť neřeší například problematiku
fontů nebo překódování do a z unikódu. Troufl bych si proto tvrdit, že
lokalizaci programu na úrovni standardní knihovny C je lepší se vyhnout.
#include <stdio.h>
#include <locale.h>
int main(void) {
printf("%f\n", 1.45);
setlocale(LC_ALL, "");
printf("%f\n", 1.45);
return 0;
}
Na počátku programu je podle normy nastavené přenositelné "C" prostředí.
Funkce printf proto vypíše číslovku s desetinnou tečkou. Volání setlocale
změní charakter prostředí ve všech (LC_ALL) oblastech
(znaková sada, výpis čísel, ...) na výchozí (parametr "") na daném počítači.
Na mém česky nainstalovaném Mandriva Linuxu 2006 s gcc 4.0.1 a glibc 2.3.5
uvedený program oddělí při druhém volání printf desetinná místa čárkou.
Na českých Windows 98 s MS Visual C++ 6.0 se uvedený program choval stejně, ale
níže uvedený převod národních znaků na velká písmena pomocí toupper mi fungoval
pouze s gcc na Linuxu.
Lokalizace prostředí na úrovni standardní knihovny je poměrně nebezpečná
záležitost. Uživatel možná rád uvidí desetinou čárku místo tečky (toho
ovšem lze snadno docílit i bez speciální podpory standardní knihovny),
ale pokud jiná část programu například pomocí printf generuje SQL dotazy,
kde musí být oddělovačem tečka, je problém na světě. Proto, pokud programátor
potřebuje nastavovat locale, je vhodné místo všech oblastí (LC_ALL) použít
pouze tu, kde je to nezbytné, například LC_NUMERIC, LC_CTYPE, LC_MESSAGES a
podobně. Zájemce, které se mi nepodařilo odradit, odkazuji na manuálovou
stránku setlocale.
Práce se znaky
Hlavičkový soubor ctype.h obsahuje řadu funkcí a maker pro detekci typu
znaku a konverzi mezi malými a velkými písmeny. Hodit by se mohly zejména
tyto rutiny s dostatečně výmluvnými názvy:
int islapha(int c); /* Je znak písmeno? */
int isdigit(int c); /* Je znak číslice? */
int isalnum(int c); /* Je znak písmeno nebo číslice? */
int islower(int c); /* Je znak malé písmeno? */
int isupper(int c); /* Je znak velké písmeno? */
int isprint(int c); /* Je znak tisknutelný? */
int isspace(int c); /* Jde o "bílý" znak? (mezera, tab, ...) */
int tolower(int c); /* Velká písmena na malá, ostatní znaky nechat. */
int toupper(int c); /* Malá písmena na velká, ostatní znaky nechat. */
Co vlastně je nebo není písmeno závisí na znakové sadě a tím pádem i na
locale. Ve výchozím nastavení "C" se za písmena považují pouze znaky anglické
abecedy. Při nastavení českého locale, budou rutiny fungovat i pro znaky
s diakritikou, ale jen s kvalitním překladačem/libc a jen pokud používáte
stejnou znakovou sadu, kterou považuje překladač/libc za výchozí pro
zvolené locale.
#include <ctype.h>
...
char *pc;
int silne = 0;
pc = heslo;
while (*pc) {
if (isdigit(*pc)) {
silne = 1;
break;
}
}
if (!silne) {
puts("Slabé heslo, neobsahuje ani jednu číslici!");
}
Ukončení programu
Zatím jsme vždy ukončovali chod programu pomocí return ve funkci main.
V některých případech může být tento postup nešikovný, běžným případem jsou
rekurzivní nebo jiná vnořená volání funkcí, kdy program zjistí, že by se měl
ukončit v nejvnitřnějším volání a je příliš komplikované zajistit návrat
ze všech instancí všech funkcí. Dalším případem je ukončení programu
programátorem knihovny, který nemůže ovlivnit volající kód. Standardní
knihovna proto nabízí funkce abort, exit a atexit.
#include <stdlib.h>
void abort(void);
void exit(int status);
int atexit(void (*function)(void));
Funkce abort zajistí okamžité chybové ukončení programu, na linuxu signálem
SIGABRT. Buffery všech otevřených souborů by měly být zapsány a soubory
uzavřeny, ale programátor by na to pochopitelně neměl spoléhat. Funkce je
určena především pro nestandardní ukončení programu při ladění, případně
po nějaké velmi vážné chybě programu, kdy už nemá smysl snažit se cokoli
zachránit a kdy je záměrem uživatele informovat následující hláškou:
[honza@localhost ~]$ ./abort
Neúspěšně ukončen (SIGABRT)
[honza@localhost ~]$
Ukončení programu mimo main nemusí vždy znamenat chybu, případně chyba není
tak vážná, aby bylo nutné program ukončovat nestandardním způsobem. V tom
případě použijeme funkci exit. Její volání (včetně parametru) odpovídá
return ve funkci main. Jedná-li se o řádné ukončení programu, voláme exit(0),
v opačném případě s nějakou nenulovou hodnotou nebo lépe exit(EXIT_SUCCESS)
a exit(EXIT_FAILURE).
Občas se hodí provést na konci programu nějakou akci bez ohledu na to, zda
byl programu ukončen returnem v main nebo funkcí exit. Není jednoduché
toho dosáhnout, neboť program může být ukončen i z knihovny, kterou nemá
programátor pod kontrolou. Naštěstí existuje funkce atexit, která zaregistruje
uživatelskou funkci, jež bude runtimem zavolaná v době ukončení programu.
Je-li funkcí více, volají se v opačném pořadí, než byly zaregistrovány.
#include <stdio.h>
#include <stdlib.h>
void po_main(void) {
puts("po_main");
}
void nakonec(void) {
puts("nakonec");
}
int main(void) {
puts("main - začátek");
puts("registruji nakonec");
atexit(nakonec);
puts("registruji po_main");
atexit(po_main);
puts("main - konec pomocí exit");
exit(EXIT_SUCCESS); /* Jen test funkce exit, return by to zvládnul stejně. */
puts("Sem bych se neměl dostat...");
return 0;
}
Ladění s pomocí assert
Při vývoji větších projektů je třeba počítat s chybami. Nejobtížněji se hledají
chyby typu přepsání paměti za polem nebo přístupu na neinicializovaný ukazatel,
neboť se projevují jen někdy a často až dodatečně a daleko od místa chyby.
Do programu se proto běžně přidává kód, který se snaží chyby odhalit již
v místě jejich vzniku a zpravidla jen vypíše chybovou hlášku a ukončí program.
Podobný kód však může zbytečně zdržovat již odladěný program a navíc
v distribuční verzi (zejména komerčního software) může být někdy přiznání chyby
a násilné ukončení programu větší zlo než
chyba sama o sobě. Již víme, že s pomocí makra preprocesoru a podmíněného
překladu můžeme vyvolávat ladící testy jen ve vývojové verzi programu.
Standardní knihovna se nám to snaží zjednodušit a nabízí rutinu assert.
#include <assert.h>
int main(void) {
int pole[10];
int i;
for (i = 0; i <= 10; i++) {
assert(i < sizeof(pole) / sizeof(int));
pole[i] = 0;
}
return 0;
}
V příkladu se snažím vynulovat pole, ale dopustil jsem se známé chyby "+1",
kdy přistupuji o jeden prvek za konec pole. Chování programu v tomto případě
není definované, záleží na tom, co se nachází za polem. Naštěstí jsem vložil
do cyklu ladící test assert, který v případě nesplnění podmínky ukončí
program pomocí funkce abort a vypíše chybovou hlášku:
assert: assert.c:10: main: Assertion `i < sizeof(pole) / sizeof(int)' failed.
Ladící testy můžeme snadno vypnout, pokud definujeme makro NDEBUG. Rozhodující
je přitom místo (posledního) inkludu hlavičkového souboru assert.h,
#define NDEBUG tedy musí předcházet #include <assert.h>.
Nejlepší je ovšem definice pomocí parametru překladače:
gcc assert.c -o assert -DNDEBUG
Uvedený příklad je trochu vykonstruovaný. Programátor, který správně testuje
meze polí přímo v cyklu pravděpodobně neudělá chybu v ukončovací podmínce
toho samého cyklu. V praxi se spíše setkáme s testováním parametrů funkcí,
užitečné je to zejména pokud jeden programátor napíše knihovnu poskytující
sadu funkcí (jejichž parametrem například nesmí být NULL) a jiný programátor
tuto knihovnu používá.
Pokračování příště
Standardní knihovnu jsme již probrali. V příštím dílu se zamyslíme nad tím,
co ve standardní knihovně nejvíce chybí. Vydalo by to na několik dalších
samostatných seriálů, proto vždy uvedu jen stručný popis a především odkaz
na web, který se problematice věnuje. Ze základní syntaxe jazyka C nám chybí
výčtový typ, i na něj dojde v příštím dílu.