Knihovna string.h II
S knihovnou <string.h> jste se již mohli setkat, když jsem povídal o funkcích pracujících s řetězci. V této kapitole se budu zabývat funkcemi, které jsou také z knihovny <string.h>, ale jejich funkce není vždy určena jen pro práci s řetězci, ale jde spíše o funkce pracující s pamětí. Najdete zde funkce, které dokáží vynulovat zadanou oblast paměti (zadanou pomocí délky a ukazatele), nebo zkopírovat (rychle a efektivně) část paměti z jednoho místa na druhé, atp.
Výčet funkcí
Pamatujte si, že funkce, které zde jsou, pracují s daty po bytech. Pokud tedy budete mít pole typu int a použijete funkci, která vyplní pole třeba jedničkou, pak to neznamená, že v každém prvku tohoto pole bude jednička, ale v každém bajtu. Pro 16bitové překladače to znamená, že typ int má velikost dvou bajtů, a číslo 1 se v něm tedy objeví dvakrát! (0000000100000001bitově = 257decimálně).
- Porovná prvních n bajtů pamětí označených ukazateli s1 a s2. Vrací 0 v případě, že jsou všechny bajty stejné; hodnotu větší jak 0, když s1 je větší jak s2; hodnotu menší jak 0, když s1 je menší jak s2.
- Zkopíruje n bajtů z paměti označené pomocí src do dest. Vrací ukazatel na dest. Paměti se nesmějí překrývat.
- Zkopíruje n bajtů z paměti označené pomocí src do dest.
Vrací ukazatel na dest. Paměti se smějí překrývat. Pracuje pomaleji než
memcpy()
. - Vyplní prvních n bajtů paměti označené ukazatelem s1 hodnotou proměnné c. Vrací ukazatel s1.
Není těch funkcí mnoho, že. Pokud jste ti šťastnější a používáte
k programování Linux, zkuste nápovědu
man bstring
. Najdete
tam další podobné funkce. Nejedná se však o standardní funkce,
proto je třeba dobře zvážit, zda jejich použití stojí za snížení
přenositelnosti kódu.
Příklad použití
V příkladu uvidíte použití funkcí pro práci s pamětí. Všimněte si
práce s ukazateli.
Při práci s těmito funkcemi si dávejte pozor, aby nemohlo nikdy dojít k pokusu
o zápis mimo alokovanou paměť. Pamatujte si, že používání těchto
funkcí je optimální co do rychlosti. Pokud chcete zkopírovat
strukturu T2 do T1, je jistě nejrychlejší použít
přiřazení T1 = T2;, ale jak je v příkladu
ukázáno, pokud kopírujete jen několik částí ze struktury ležících za sebou,
je výhodnější použít memcpy()
než přiřazovat
jednu položku struktury za druhou. Obdobně se může memcpy()
hodit při kopírování pole struktur.
/* c25/pamet1.c */
#include <stdio.h>
#include <string.h>
#ifdef _MSC_VER
#define ZU "Iu"
#else
#define ZU "zu"
#endif
void porovnej(const char *s1, const char *s2, size_t n)
{
int x;
x = memcmp(s1, s2, n);
printf("%2" ZU ": %s %s %s\n", n, s1, x < 0 ? "<" : x > 0 ? ">" : "=", s2);
}
typedef struct {
short int x;
char pole[10];
float f;
} Trida;
void tiskni(const Trida * T)
{
unsigned int i;
printf("T = {%3i,{", T->x);
for (i = 0; i < sizeof(T->pole) / sizeof(T->pole[0]); i++)
printf("%i,", T->pole[i]);
printf("\b},{%.40f}}\n", T->f);
}
int main(void)
{
Trida T1, T2;
char text1[] = "1234567890";
char text2[] = "abcdefghij";
porovnej("aaazzzzz", "aabaa", 4);
porovnej("aaaaa", "aabaa", 2);
tiskni((Trida *) memset(&T1, 0, sizeof(T1)));
tiskni((Trida *) memset(&T2, 1, sizeof(T2)));
/* Vzpomente si na aritmetiku ukazatelu. Vyraz &T1 + n neukazuje n
* bajtu za adresu &T1, ale n*sizeof(T1) bajtu za adresu T1. Tedy
* o n * struktur dale. Proto pouziji k pretypovani ukazatele typ
* char, ktery jiz ma velikost 1 bajtu
* a tak muzu pomoci sizeof posouvat ukazatele po bajtech */
memcpy((char *) &T1 + sizeof(T1.x), (char *) &T2 + sizeof(T1.x),
sizeof(T1) - sizeof(T1.x));
tiskni(&T1);
printf("text1+3: %s\n", (char *) memcpy(text1 + 3, text2 + 6, 3));
printf("text1 : %s\n", text1);
/* chybne pouziti - pameti se prekryvaji */
(void) memcpy(text2 + 1, text2, 6);
printf("text2+1: %s (memcpy)\n", text2);
/* spravne pouziti */
memcpy(text2, "abcdefghij", 10);
(void) memmove(text2 + 1, text2, 6);
printf("text2+1: %s (memmove)\n", text2);
return 0;
}
/*------------------------------------------------*/
Popis definice ZU
viz datový typ pro ukazatel.
Výstup z programu:
4: aaazzzzz < aabaa 2: aaaaa = aabaa T = { 0,{0,0,0,0,0,0,0,0,0,0},{0.0000000000000000000000000000000000000000}} T = {257,{1,1,1,1,1,1,1,1,1,1},{0.0000000000000000000000000000000000000237}} T = { 0,{1,1,1,1,1,1,1,1,1,1},{0.0000000000000000000000000000000000000237}} text1+3: ghi7890 text1 : 123ghi7890 text2+1: aabbdefhij (memcpy) text2+1: aabcdefhij (memmove)
K tomuto programu je třeba ještě něco dodat. Porovnávání struktur
pomocí memcmp()
je poněkud sporné
co do přenositelnosti kódu.
Některé platformy (ať už softwarové nebo hardwarové) mohou mezi
jednotlivé části struktury vkládat bity (z jakéhosi optimalizačního důvodu to
opravdu dělají, zvláště překladače C++). Tyto bity pak mohou mít
různé hodnoty i pro struktury, jejichž obsah je stejný.
Z toho důvodu může funkce memcmp()
vyhodnotit stejné struktury jako různé. Jediným řešením tohoto problému
je porovnávání všech složek struktur hezky jedné po druhé.
Funkce sprintf(), snprintf() a sscanf()
Funkce sprintf()
a
snprintf()
nejsou funkcemi z knihovny <string.h>, ale z knihovny <stdio.h>.
Jelikož však pracují také s pamětí, jako výše probírané funkce, a s řetězci,
dovolil jsem si je sem na závěr „přifařit“.
Jejich deklarace jsou:
Funkce sprintf()
zapíše formátovaný
výstup stejně jako funkce printf(), jen s tím rozdílem, že
výstup jde do paměti, kam ukazuje str. Funkce
snprintf()
navíc zapíše pouze nanejvýš
size znaků (lol, to je věta xD). Funkce
snprintf()
je tedy bezpečnější co
do rizika překročení limitu
alokované paměti. Funkci sprintf()
byste tedy měli používat jen v případě, že si můžete být jisti,
že formátovaný řetězec nepřekročí délku pole
str.
Funkce sprintf()
je rychlejší než
snprintf()
, protože nekontroluje délku
vytvářeného řetězce. Ovšem za tu cenu, že může způsobit neoprávněný
přístup do paměti.
Obě funkce vracejí počet úspěšně zapsaných znaků do paměti.
Pokud si vzpomínáte na funkce, které převádějí řetězce na čísla
(např. atof()), pak opačný převod
se provádí právě pomocí funkce snprintf()
.
Funkce snprintf()
je součást standardu C99, který VS nepodporuje,
takže si tenhle příklad ve Visual Studiu nepřeložíte.
Výstup z programu:
Retezec 3.80 je dlouhy 4 znaku.
Všimněte si, že řetězec je dlouhý o jeden znak méně, než byl zadaný
limit pro funkci snprintf()
. Ten poslední znak
je totiž vyhrazen pro označení konce řetězce '\0'
.
Za domácí úkol můžete vylepšit příklad vprintf1.c tak, aby byl rámeček kolem tisknutého řetězce skutečně rámeček – aby se jeho délka měnila podle délky tisknutého textu.
Funkce sscanf()
Funkce sscanf()
je též ze souboru
<stdio.h> a dělá totéž co scanf(),
jenom místo standardního vstupu čte data z paměti, kam ukazuje
str. Deklarace funkce je následující:
Myslím, že její použití při porovnání s funkcí scanf()
je natolik zřejmé,
že není nutné uvádět příklad (nebo jsem jen líný jej uvést :-).