A típusok kapcsán feltűnhetett, hogy C-ben nem létezik a szöveg típus koncepciója, de ez, mint láthattuk a helló világ program esetén, nem jelenti azt, hogy egyáltalán nincs lehetőségünk szövegek kezelésére. De akkor hogy is van ez megvalósítva? Karakterek tömbjeként természetesen. Ez némiképpen komplikálja a dolgokat. Mégpedig azért, mert karakterekből létezik az ASCII char
típus és az UTF wchar_t
típus. Ez azért komplikálja a dolgokat, mert ha egy char *
pointernek akarunk értékül adni egy wchar_t
tömböt, vagy mutatót, akkor az nem fog menni. Először a karaktereket konvertálgatnunk kell a kettő között.
A char *
típusú szövegből wchar_t
típusba és vissza konvertálás gyakori probléma. Ennek az oka az, hogy a régen írt C kódok az ASCII kódot feltételeztek, az újabb API-k és könyvtárak pedig inkább UTF-el működnek. Azonban a C történelme alatt számos könyvtár és komponens készült, ami sosem lett UTF-re átírva. Ebből adódóan ha a programunkat úgy is írjuk meg, hogy csak wchar_t
legyen benne, akkor se tudjuk elkerülni, hogy előbb-utóbb ne kelljen a két típus között konvertálnunk.
Az alábbi program a konvertálást mutatja be:
#include <stdio.h>
/*wcsnlen_s, strnlen_s */
#include <string.h>
/*wcstombs és mbstowcs*/
#include <stdlib.h>
int main()
{
wchar_t utf[] = L"UTF szoveg volt";
char *asciiKonvertalt;
wcstombs(asciiKonvertalt, utf, wcsnlen_s(utf, 20));
printf("%s\r\n", asciiKonvertalt);
char ascii[] = "Ez egy ascii string volt";
wchar_t *utfKonvertalt;
mbstowcs(utfKonvertalt, ascii, strnlen_s(ascii, 25));
wprintf(L"%S\r\n", utfKonvertalt);
return 0;
}
A program kimenete:
UTF szoveg volt
Ez egy ascii string volt
A fordító számára azt, hogy egy idézőjelek között megadott szöveget wchar_t *
típusként értelmezzen az L
karakter prefixálásával fejezzük ki, mivel ez egy hosszú karakterekből álló szöveg.
A wcstombs
függvény segítségével tudunk wchar_t *
típusból char *
típusra konvertálni. Ennek a függvénynek az első paramétere a cél ASCII tömb, a második pedig a forrás UTF tömb.A harmadik paramétere a konvertálandó karakterek száma. Ez a wcslen
függvénnyel kérdezhető le egy wchar_t *
típusú szöveg esetén. A példában azonban a wcsnlen_s
kerül alkalmazásra, ami ennek a függvények a biztonságos változata.
Ha egy függvénynek van biztonságos változata, azt a neve utáni _s
jelzéssel szokták jelölni. De mitől nem biztonságos egy szöveg hossz lekérdezés? A szövegek végét a \0
karakter szokta jelölni a memóriában, amire gondolni kell a szövegek méretének allokálása esetén. Vagyis egy 40 elemű karakter tömbbe valójában csak 39 karaktert tudunk elhelyezni, mivel egy karakter kell a vég jelzésére is.
A wcslen
a karaktereket számolja meg egy szövegben addig, amíg nem talál egy \0
karaktert. Azonban, ha a szövegben nincs \0
karakter, akkor ez hatalmas méretet fog visszaadni1, ami biztos, hogy a teljes memória felülírásához fog vezetni. Ha sikerül felülírni a memóriát így, akkor annak megjósolhatatlan következményei lehetnek. Főleg, ha a szöveg felhasználói bevitel eredménye. Ilyen esetben könnyen előfordulhat, hogy a támadó szándékú user direkt egy rossz szöveget preparál, ami egy tömb túlindexeléssel párosítva például tetszőleges kód végrehajtásra adhat lehetőséget.
A wcsnlen_s
ezzel szemben attól lesz biztonságos, hogy a második paraméterének meg kell adni egy maximális méretet, ami korlátozza a memória foglalás méretét.
Visszafelé, ASCII-ból UTF-be a mbstowcs
függvénnyel tudunk konvertálni. A paraméterezés itt is hasonló. Az első a cél tömb, a második paraméter a forrás tömb, a harmadik pedig a karakterek száma. ASCII esetén a strnlen_s
a biztonságos változata az strlen
függvénynek, ami egy char *
típusú szöveg hosszát adja vissza.
Kiírásnál érdekesség, hogy a wchar_t *
kiírásra a wprintf
használható, ami a printf
wchar_t *
típussal működő változata.
UTF-8
Az UTF-8 egy érdekes szabvány C esetén, mivel ez egy változó bithosszúságú kódolás. Ha egy UTF karakter belefér 1 byte-ba, akkor a tárolása 1 byte-on történik, ha pedig több byte kell, akkor vagy 2 vagy 4 byte-on tárolódik az adott karakter. Szépsége ennek a kódolásnak, hogy ASCII kompatibilis és a legtöbb fordító kezelni is tudja az UTF-8 kódolású C forráskódokat. Ebből adódóan, ha a terminálra szeretnénk írni UTF-8 kódolással, akkor a char *
és a printf
is ugyanúgy használható.
Azonban, hogy ne kapjunk „szemetet” kiíráskor, a terminál programunknak is támogatnia kell az UTF-8-at, hogy a szövegek megfelelően jelenjenek meg. Az alábbi példa az UTF-8 használatát mutatja be Windows és cmd.exe esetén:
#include <stdio.h>
/*strnlen_s miatt*/
#include <string.h>
/*system miatt*/
#include <stdlib.h>
int main()
{
system("chcp 65001");
char *utf8Text = "Árvíztűrő tükörfúrógép";
printf("%s\n", utf8Text);
char *emoji = "🐰";
printf("strlen(emoji): %d", strnlen_s(emoji, 10));
return 0;
}
A program kimenete:
Árvíztűrő tükörfúrógép
strlen(emoji): 4
A system
hívás átállítja a CMD kimenetét UTF-8 kompatibilisre. Erre azért van szükség, mert a CMD kimenete kompatibilitási okokból ASCII alapú. Ez kihagyható is lenne, de ebben az esetben akkor nekünk kellene manuálisan lefuttatni a chcp 65001 > nul
parancsot a program futtatása előtt.
A chcp 65001
futtatásának kivétel nincs semmi különleges a programunkban, a korábban bemutatott módszerekkel tudunk ékezetes és különleges írásjeleket alkalmazni. Azonban ebben az esetben és wchar_t
esetén sem szabad megfelejtkeznünk arról, hogy a karakterek száma nem egyenlő az írásjelek számával. Ez látható a 🐰
szöveg hosszának lekérdezésekor. Ennek eredménye 4 lesz, mivel 4db karakter segítségével fejezhető ki az UTF-8 kódolt írásjel.
A szimbólumok számának megállapítására a C beépítetten nem tartalmaz függvényt, mivel az UTF-8 alapú működést a fordító „butasága” teszi lehetővé: Nem érdekli a bemenet kódolása és a kimenet kódolása sem, szóval valójában egy nagyon hasznos „workaround” megoldás ez a fajta kimenet és szöveg kezelés. Cserébe viszont egy külső unicode könyvtárat kell használnunk.
-
Szabvány szerint szintén undefined behaviour. Hatalmas méretet azért tud visszaadni, mert valahol jó eséllyel találni fog egy
\0
karaktert a memóriában, de könnyen lehet, hogy nem és a program összeomlik.↩