Práce s binárními soubory a pohyb v souborech

Textové soubory mají tu výhodu, že je jsou zapisovány (jak čísla, tak text) v ASCII znacích, tedy čitelně pro člověka. Navíc funkce pro práci se soubory pracující v textovém režimu „rozumí“ speciálnímu znaku pro konec řádky '\n'. To pro binární soubory neplatí. Funkce probírané v kapitole o textových souborech vám umožňovali například zapsat nebo načíst číslo, řádku, slovo atp. uložené v ASCII znacích. Naproti tomu v binárních souborech se ukládají data tak, jak je ukládáte v proměnných.

Nic vám nebrání mít v binárních souborech uloženy jen ASCII znaky. Záleží jen na úhlu pohledu, zda budete považovat soubor za textový, nebo binární. Říkám to proto, abyste si uvědomili, že zde probírané funkce (např. fread() a fwrite()) je možné beze všeho použít i na textové soubory, ale funkce vytvořené pro čtení z textových souborů (tedy např. fscanf()) očekávají textový formát souboru (tedy ASCII znaky), a proto není dvakrát vhodné je používat na binární soubory.

Vše popisované v této části je použitelné jak pro textové, tak binární soubory. Někde je to jen vhodnější a někde méně.

Čtení a zápis binárního souboru

Otevření a uzavření binárního souboru je stejné jako textového, jen s tím rozdílem, že se v druhém argumentu funkce fopen() místo příznaku t napíše b. Vše ostatní (včetně např. ošetřování chyb) je stejné.

V Linuxu dokonce žádný rozdíl mezi textovým a binárním přístupem k souboru není (znak nové řádky je jeden znak). Přesto, kvůli přenositelnosti, je dobré příznak b nebo t používat.

Zápis pomocí fwrite()

Funkce fwrite() slouží k zápisu dat do souborového proudu. Její deklarace je následující:

size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

Použití této funkce je snazší, než se na první pohled zdá. Prvním argumentem je ukazatel ptr, který ukazuje na začátek místa v paměti, odkud se budou brát data pro zápis do souboru. Může to být ukazatel na strukturu, řetězec, nebo číslo atp. Druhý argument, size, je velikost položky v bajtech, kterou budete zapisovat (může to být např. sizeof(struktura)). Třetím argumentem je nmemb, což je počet položek, které zapíšete. Celkem se tedy zapíše nmemb * size bajtů. A čtvrtým argumentem není nic jiného než datový proud stream, do kterého se data zapisují.

Návratovou hodnotou je počet zapsaných položek, v ideálním případě je to rovno nmemb. V případě chyby je nastavena globální proměnná errno.

Čtení pomocí fread()

Funkce fread() slouží ke čtení dat ze souborového proudu. Její deklarace je následující:

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

Funkce fread() načte nmemb dat o velikosti size ze souboru stream do paměti kam ukazuje ptr.

Návratovou hodnotou je počet správně načtených položek, v ideálním případě nmemb. Pokud se nenačtou všechny požadované položky, pak došlo k chybě, nebo se funkce dostala na konec souboru. To lze zjistit pomocí funkcí feof() a ferror().

Příklad čtení a zápisu

V příkladu zapíši do souboru strukturu data a pak ji znovu načtu a vypíšu.

Všimněte si velikosti vytvořeného souboru. Uvidíte, že velikost odpovídá velikosti pěti zapsaných struktur. Pole text, které je položkou struktury data se zapíše celé, jako pole sta znaků, a ne jenom textový řetězec v položce text.

  1. /*------------------------------------------------*/
  2. /* c33/binar.c                                    */
  3. #define _CRT_SECURE_NO_WARNINGS
  4. #include <errno.h>
  5. #include <stdio.h>
  6.  
  7.     float cisla[5];
  8.     char text[100];
  9. } data;
  10.  
  11. #define NAZEV "bsoub.dat"
  12.  
  13. int main(void)
  14. {
  15.     FILE *soubor;
  16.     /* pole obsahuje 5 struktur typu data */
  17.     data pole[] = {
  18.         { {1.1, 1.2, 1.3, 1.4, 1.5}, "prvni struktura"},
  19.         { {2.1, 2.2, 2.3, 2.4, 2.5}, "druha struktura"},
  20.         { {3.1, 3.2, 3.3, 3.4, 3.5}, "treti struktura"},
  21.         { {4.1, 4.2, 4.3, 4.4, 4.5}, "ctvrta struktura"},
  22.         { {5.1, 5.2, 5.3, 5.4, 5.5}, "pata struktura"}
  23.     };
  24.     data xdata;
  25.     size_t navrat;
  26.     size_t i;
  27.  
  28.     if ((soubor = fopen(NAZEV, "wb")) == NULL) {
  29.         /* vite co znamena "w" ? */
  30.         fprintf(stderr, "%s %i:", __FILE__, __LINE__);
  31.         perror(NULL);
  32.         return errno;
  33.     }
  34.  
  35.     /* zapis dat do souboru - nejdriv prvnich tri a pak zbylych dvou */
  36.     navrat = fwrite(pole, sizeof(data), 3, soubor);
  37.     if (navrat != 3) {
  38.         fprintf(stderr, "%s %i:", __FILE__, __LINE__);
  39.         perror(NULL);
  40.         if (EOF != fclose(soubor))
  41.             fprintf(stderr, "S jistotou bylo zapsano %u polozek\n",
  42.                     navrat);
  43.         else
  44.             perror(NULL);
  45.         return errno;
  46.     }
  47.  
  48.     navrat = fwrite(pole + 3, sizeof(data) * 2, 1, soubor);
  49.     if (navrat != 1) {
  50.         fprintf(stderr, "%s %i:", __FILE__, __LINE__);
  51.         perror(NULL);
  52.         fclose(soubor);
  53.         fprintf(stderr, "Neni znam pocet zapsanych polozek\n");
  54.         return errno;
  55.     }
  56.  
  57.     /* soubor pred ctenim uzavru a pak zase otevru */
  58.     if (fclose(soubor) == EOF)
  59.         perror(NULL);
  60.  
  61.     if ((soubor = fopen(NAZEV, "rb")) == NULL) {
  62.         fprintf(stderr, "%s %i:", __FILE__, __LINE__);
  63.         perror(NULL);
  64.         return errno;
  65.     }
  66.  
  67.     /* nyni data nactu a vypisu */
  68.     do {
  69.         navrat = fread(&xdata, 1, sizeof(xdata), soubor);
  70.         if (navrat != sizeof(xdata)) {
  71.             if (feof(soubor)) {
  72.                 printf("Konec souboru!\n");
  73.                 break;
  74.             } else {
  75.                 perror(NULL);
  76.                 fprintf(stderr, "Bylo nacteno pouze %d bajtu "
  77.                         "z jedne polozky spravne\n", navrat);
  78.                 break;
  79.             }
  80.         }
  81.  
  82.         /* vypasni struktury data */
  83.         for (i = 0; i < sizeof(xdata.cisla) / sizeof(xdata.cisla[0]); i++)
  84.             printf("%3.1f ", xdata.cisla[i]);
  85.         printf("%s\n", xdata.text);
  86.  
  87.     } while (!feof(soubor));
  88.  
  89.     if (fclose(soubor) == EOF)
  90.         perror(NULL);
  91.  
  92.     return 0;
  93. }
  94.  
  95. /*------------------------------------------------*/
Visual Studio

Makro _CRT_SECURE_NO_WARNINGS je tu kvůli funkci fopen(), viz scanf().

Výstup z programu:

1.1 1.2 1.3 1.4 1.5 prvni struktura
2.1 2.2 2.3 2.4 2.5 druha struktura
3.1 3.2 3.3 3.4 3.5 treti struktura
4.1 4.2 4.3 4.4 4.5 ctvrta struktura
5.1 5.2 5.3 5.4 5.5 pata struktura
Konec souboru!

Všimněte si, že po přečtení páté struktury ještě funkce feof() nevratcí hodnotu „true“, ale vrací ji až poté, co se pokouším načíst strukturu šestou, která již v souboru neexistuje. Příznak konce souboru je tedy nastaven až poté, co se pokusíte načíst více dat, než je v souboru a ne ve chvíli, kdy přečtete poslední existující data ze souboru. Je to možná ošklivé, ale je to tak.

Pozice v souboru

Představte si soubor o velikosti 10 bajtů. Když takový soubor otevřete pro čtení, ukazuje souborový indikátor pozice (dále jen kurzor) na začátek souboru (tedy na „nultý“ bajt). Pokud použijete libovolnou funkci na přečtení prvního bajtu, kurzor bude ukazovat na druhý bajt (bajt číslo 1). Přečtete-li dalších 5 bajtů, bude ukazovat na šestý bajt atd. Takto můžete přečíst celý soubor hezky bajt za bajtem. Ale co když chcete přečíst zase začátek souboru? Zavřít a zase otevřít soubor je dost nepraktická, pomalá věc. A co když chcete přečíst jenom posledních 5 bajtů souboru? Pokud má nějaký soubor třeba 1 GiB, máte jej snad celý přečíst jen kvůli pár bajtům na konci?
Pokud otevřete soubor pro zápis, ukazuje kurzor na konec souboru. Co když ale chcete zapsat něco doprostřed souboru?

Kvůli výše popsaným situacím vzniklo několik funkcí, které vám umožní snadno a rychle nastavit kurzor na požadovanou pozici. První z těchto funkcí je fseek().

int fseek(FILE *stream, long offset, int whence);

Funkce fseek() pracuje se souborovým proudem stream. Posouvá kurzor do vzdálenosti offset (tato hodnota může být i záporná!) od místa whence.

Místo whence může být SEEK_SET (relativní vzdálenost od začátku souboru), SEEK_CUR (relativní vzdálenost od aktuální pozice) nebo SEEK_END (relativní vzdálenost od konce souboru).
Funkce vrací v případě úspěchu nulu.

Další používané funkce jsou ftell() a rewind().

long ftell(FILE *stream);
void rewind(FILE *stream);

Funkce rewind() posune kurzor na začátek souboru stream. Je to totéž, jako (void) fseek(stream, 0L, SEEK_SET).
Funkce ftell() vám zjistí hodnotu aktuální pozice kurzoru v souboru stream.

Alternativou k předešlým funkcím mohou být funkce fgetpos() a fsetpos(). Tyto funkce pracují s objektem typu fpos_t, do kterého funkce fgetpos() ukládá hodnotu aktuální pozice kurzoru a fsetpos() nastavuje pozici kurzoru podle této proměnné.

int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);

Všechny tyto funkce jsou vhodné pro používání jak v textovém, tak binárním přístupu k souboru.

Zjištění velikosti souboru

Nejlepším způsobem jak zjistit velikost souboru je přesunout kurzor na konec souboru a pak (funkcí ftell()) zjistit aktuální pozici kurzoru. V příkladu si vyzkoušíte práci s kurzorem a zjištění velikosti souboru. Pro jednoduchost 1) nebudu v programu ošetřovat žádné chyby.

  1. /*------------------------------------------------*/
  2. /* c33/ftell1.c                                   */
  3. #define _CRT_SECURE_NO_WARNINGS
  4. #include <stdio.h>
  5. #include <stddef.h>
  6.  
  7. int main(void)
  8. {
  9.     FILE *soubor;
  10.     size_t i;
  11.     char pole[100];
  12.     soubor = fopen("pokus.txt", "w+t");
  13.  
  14.     /* nejdrive zapisi do souboru nekolik znaku 'a' */
  15.     for (i = 0; i < 50; i++)
  16.         fputc('a', soubor);
  17.  
  18.     /* posunu kurzor za prvnich 5 bajtu */
  19.     fseek(soubor, 5L, SEEK_SET);
  20.     /* zapisi 5 znaku '-' */
  21.     for (i = 0; i < 5; i++)
  22.         fputc('-', soubor);
  23.  
  24.     /* posunu se na konec souboru a zjistim jeho velikost */
  25.     fseek(soubor, 0L, SEEK_END);
  26.     printf("Velikost souboru je %lu bajtu.\n", ftell(soubor));
  27.  
  28.     /* presunu se opet na zacatek souboru a prectu jeho obsah */
  29.     fseek(soubor, 0L, SEEK_SET);
  30.     while (!feof(soubor)) {
  31.         fgets(pole, sizeof(pole) / sizeof(char) - 1, soubor);
  32.         printf("%s", pole);
  33.     }
  34.     printf("\n");
  35.     fclose(soubor);
  36.     return 0;
  37. }
  38.  
  39. /*------------------------------------------------*/

Výstup z programu:

Velikost souboru je 50 bajtu.
aaaaa-----aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

1) Pro jednoduchost programu, ne moji :-).

Komentář Hlášení chyby
Created: 29.8.2003
Last updated: 26.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..