V jazyce C existuje pointer bez informace o typu hodnoty, na kterou
pointer ukazuje. To je užitečné například v situaci, kdy pracujeme čistě
s pamětí a nepotřebujeme znát, jakého je typu. Například při kopírování,
porovnávání apod. (K tomu se ještě dostaneme). Typ takového pointeru je
void*
, programátoři mu říkají void pointer.
Void pointer lze implicitně přetypovat na pointer jakéhokoliv typu, a pointer jakéhokoliv typu lze implicitně přetypovat na void pointer. Přetypování mezi ostatními typy pointerů je nutno provádět explicitně (jinak překladač vypíše warning nebo chybu).
Příkladem jednoduché funkce pracující s pamětí je tisk jejího obsahu na obrazovku. Jako argumenty funkce postačuje adresa začátku paměti a její velikost v bajtech.
void dump_mem(void *mem, size_t size);
Pamět budeme tisknout po bajtech do tabulky, která bude mít na každém
řádku 8 bajtů. Jeden bajt vytiskneme jako dvouciferné hexadecimální
číslo (zamyslete se proč je to vhodné). Ve funkci využijeme toho, že typ
unsigned char
má vždy velikost jeden bajt. Na začátku
funkce (implicitně) přetypujeme pointer mem
na pointer na
unsigned char
a můžeme se na pamět dívat jako na pole
bajtů. Poté stačí toto pole projít a vytisknout.
void dump_mem(void *mem, size_t size) {
unsigned char *bytes = mem; // tady pretypujeme
for (size_t i = 0; i < size; i += 1, bytes += 1) {
if (i && !(i % 8)) {
("\n");
printf}
("%.2X ", *bytes);
printf}
("\n");
printf}
Chceme-li funkci použít, musíme získat její argumenty: získat adresu začátku paměti a její velikost.
int x = 300;
(&x, sizeof(x)); // tady dochazi k implicitnimu pretypovani int* na void*
dump_mem
float f = 2.4;
(&f, sizeof(f));
dump_mem
struct { char c; int a; } s = {'x', 55 };
(&s, sizeof(s));
dump_mem
char a[] = "ahoj svete";
(a, strlen(a) + 1);
dump_mem
double b[5] = { 1.1, 2.2, 3.3, 0, 4.4 }
(b, sizeof(b)); dump_mem
Přetypování z void*
na unsigned char
je
vždy bezpečné. Jinde může dojít k nedefinovanému chování. Prvním důvodem
jsou tzv. trap reprezentace. Mohou existovat typy, kde ne každý
obsah paměti odpovídá platné hodnotě daného typu. Takový obsah paměti,
který neodpovídá platné hodnotě je past: pokus o dereferenci vede k
nedefinovanénu chování. Podle standardu jediný typ, který zaručeně nemá
trap reprezentaci právě unsigned char
. Ostatní typy trap
reprezentaci mít mohou, ale nemusí. Dalším problémem může být tzv.
zarovnání (anglicky alignment): na některých architekturách je
vyžadováno, aby vícebajtové objekty začínaly na adrese s určitou
vlastností, například na adrese dělitelné velikostí objektu. Dereference
nezarovnané adresy vede k nedefinovanému chování.
Pro explicitní konverze mezi pointery jiných typů existují ve
standardu pravidla (strict aliasing rules), která svým rozsahem do kurzu
nepatří. Doporučuji se prozatím takovým konverzím vyhýbat. (Mimo void
pointer jsou výjimkou i unsigned char
pointery).
Pro manipulaci s pamětí lze využít některé funkce ze
string.h
. Jsou to zejména:
void* memcpy(void* dest, void* src, size_t count)
Zkopíruje count
bajtů z adresy src
na
adresu dest
. Oblasti paměti odkud a kam se kopíruje se
nesmí překrývat.
void* memmove(void* dest, void* src, size_t count)
Zkopíruje count
bajtů z adresy src
na
adresu dest
. Oblasti paměti odkud a kam se kopíruje se
mohou překrývat.
void* memset(void* dest, int ch, size_t count)
Zkopíruje hodnotu (unsigned char)ch
do
count
bajtů paměti začínajících adresou
dest
.
int memcmp(void* lhs, void* rhs, size_t count)
Lexikograficky porovná prvních count
bajtů paměti na
adresách lhs
a rhs
. Vrací znaménko rozdílu
mezi hodnotami prvních dvou bajtů na kterých se lhs
a
rhs
liší. Pokud takové bajty neexistují, vrací 0.
Další podrobnosti o zmíněných funkcích si čtenář nastuduje z referenční příručky. Důležité je podívat zejména okrajové chování (např. když je některý argument 0).
Upravte funkci dump_mem
tak, aby na začátku každého
řádku vypsala adresu prvního bajtu na tomto řádku. Přidejte možnost
vypisovat bajty jako posloupnost 8 bitů (je nutné využít bitových
operátorů).
Naprogramujte vlastní verze funkcí
memcpy, memcmp, memset
. Funkce mohou být implementovány
naivně.
Najděte způsob, jak bez použití sizeof
programově
zjistit velikost nějakého typu, například float
.
Napište funkci pro detekci endianity a pro převod mezi
big endian a small endian (například pro typ
unsigned int
, nebo obecnou funkci). Wiki o
endianitě.