Méně známé skutečnosti o C a C++: Vztah mezi znakem a bajtem

Od Petr Zemek, 2010-01-09

Na námět jednoho mého čtenáře (fakt, ono zřejmě někdo ty moje příspěvky čte! :] j/k) se v tomto příspěvku budu zabývat vztahem mezi znakem (character) a bajtem (byte) v jazycích C a C++. Mimo jiné se dozvíte, proč strlen() vrací vždy počet bajtů v předané posloupnosti ukončené nulovým bajtem a proč nemůže sloužit k tomu, aby vracela počet širokých znaků (wide characters) předaného řetězce, např. pokud se jedná o řetězec znaků reprezentovaných v Unicode.

Něco na úvod

První aplikace si vystačily se znakovou sadou ASCII -- vše bylo psáno anglicky a možné i nemožné (netisknutelné) používané znaky se povedlo vměstnat do 7 bitů. Problém nastal, pokud jste psali aplikaci komunikující v jiném, než anglickém jazyce. Jako první řešení se jevilo využití 8. bitu v bajtu a tudíž na pozice 128-255 mohly být umístěny znaky vaší národní abecedy. Na tom by nebylo nic špatného, ovšem různé země používají různé znaky a nebylo je možno všechny vměstnat do onoho zbytku bajtu. Tak vznikla řada kódování, která určovala, která bitová kombinace bude reprezentovat který znak z národní abecedy (u nás se asi nejčastěji používá CP-1250 a ISO-8859-2). No jo, jenže co mají dělat země, jejichž národní abeceda obsahuje tolik znaků, že i toto je málo (např. asijské země, které používají ideografická písma)? Jako řešení tohoto problému se objevila právě standardizovaná reprezentace Unicode a více bajtová kódování (u nás asi nejznámější UTF-8), která umožnila zakódovat více abeced a znaků do jedné znakové sady (včetně znaků z dálného východu).

Znak vs bajt v C a C++

C v tomto textu bude znamenat jazyk C podle normy ISO C99 a C++ bude znamenat jazyk C++ podle normy ISO C++98.

Situace v C a C++ je taková, že standardně se uvažují a používají řetězce nad typem char (ať signed či unsigned), který má velikost vždy jeden bajt (viz příslušná norma, operátor sizeof). Pokud tedy chcete v aplikaci použít i znaky mimo ASCII, musíte použít některé z národních kódování (třeba ISO-8859-2). V případě, že toto nestačí, tak lze sáhnout po speciálním typu wchar_t (v C99 typedef na některý integrální typ, v C++ se jedná o klíčové slovo a vestavěný datový typ), který reprezentuje tzv. široký znak (wide character), který může mít velikost větší než bajt. Ano, pouze "může". Problém je totiž v tom, že skutečná velikost a některé další detaily jsou implementačně závislé (C++ 3.9.1.5, C99 7.17.2; vyplývá to z toho, že v obou jazycích je wchar_t vnitřně definován nad některým existujícím integrálním typem, který má implementačně závislou velikost). Použitím širokých znaků se zde ale nechci zabývat, takže přejděme na to, o čem tento příspěvek má být.

V jazyce C je znak (character) definován (3.7.1) jako "jednobajtový znak", tedy znak = bajt. Široký znak (wide character) je definován (3.7.2) jako bitová reprezentace, která se vleze do wchar_t. V jazyce C++ jsem takovouto definici nenašel a znakem je zde myšlen jakýkoliv objekt, který může reprezentovat text (17.1.2). Tolik k definicím ze standardu. Jak tedy v praxi rozlišit, kdy znak = bajt a kdy tomu tak není? Podle kontextu. Jako jakési "rule of thumb" bych uvedl následující pravidlo. Pokud uvidíte v kódu pouze char (ať již signed, tak unsigned), tak se znakem myslí bajt. Pokud uvidíte v kódu wchar_t, tak se znakem může myslet i více bajtů.

Vezměme si např. funkci size_t strlen(const char *), která má vracet počet znaků předaného řetězce. Je tedy jasné, že tato funkce vrací počet bajtů daného řetězce, až do nulového bajtu. Proč? Dejme tomu, že bychom tuto funkci použili pro výpočet počtu znaků nějakého řetězce nad typem wchar_t (řetězec tvořený širokými znaky). Výsledkem v tomto případě bude obecně nesmysl, protože funkce počítá až do prvního výskytu nulového bajtu (pamatujete, sizeof(char) == 1). Použitá reprezentace kódování širokých znaků ale může obsahovat vícebajtové znaky, ve kterých se vyskytují nulové bajty. V případě vícebajtovéch znaků by se muselo počítat až do prvního výskytu sizeof(wchar_t) nulových bajtů. Jaké je tedy správné řešení pro výpočet počtu znaků řetězce tvořeného vícebajtovými znaky? Použijte příslušnou funkci size_t wcslen(const wchar_t *s), která je ekvivalentem strlen() pro řetězce tvořené vícebajtovými znaky. Podobně to platí pro ostatní funkce. Pro detailnější popis použitého příkladu a problému mrkněte zde.

Závěr

To, zda se znakem myslí bajt, či nikoliv, lze poznat z kontextu. Pokud pracujeme s řetězci nad typem char, tak je znak ekvivalentní bajtu. Pokud pracujeme s řetězci nad typem wchar_t, tak typicky není znak ekvivalentní bajtu, ale je větší (toto je nicméně implementačně závislé).

Obsah tohoto pole je soukromý a nebude veřejně zobrazen.

Filtrované HTML (využíváno)

  • Povolené HTML značky: <a href hreflang> <em> <strong> <cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <table>
  • Zvýraznění syntaxe kódu lze povolit přes následující značky: <code>, <blockcode>, <bash>, <c>, <cpp>, <haskell>, <html>, <java>, <javascript>, <latex>, <perl>, <php>, <python>, <ruby>, <rust>, <sql>, <text>, <vim>, <xml>, <yaml>.
  • Řádky a odstavce se zalomí automaticky.
  • Webové a e-mailové adresy jsou automaticky převedeny na odkazy.
CAPTCHA
16 + 1 =
Vyřešte tento jednoduchý matematický příklad a vložte výsledek. Např. pro 1+3 vložte 4.
Nějak se mi tady rozmohl spam, takže poprosím o ověření.