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.
void *memcpy(void *dest, const void *src, size_t n);
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.
void *memmove(void *dest, const void *src, size_t n);
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().
void *memset(void *s1, int c, size_t n);
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;
}

/*------------------------------------------------*/
Visual Studio

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:

int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format,...);

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().

/*------------------------------------------------*/
/* c25/snpr1.c                                    */

#include <stdio.h>
#include <string.h>

int main(void)
{
    char str[50];
    snprintf(str, 5, "%f", 3.8);
    printf("Retezec %s je dlouhy %zu znaku.\n", str, strlen(str));
    return 0;
}

/*------------------------------------------------*/
Visual Studio

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í:

int sscanf(const char *str, const char *format, ...);

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 :-).

Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 23.6.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..