Jazyk C 2013 – 5. cvičenie

Dynamicky alokované polia

Maximum z nameraných teplôt [premenlivý počet hodnôt]

V textovom súbore sa nachádza niekoľko teplôt (reálne čísla), pričom prvý riadok súboru obsahuje číslo udávajúce počet údajov v súbore. Nájdite najväčšiu nameranú teplotu.

Príklad súboru:

3 -7.2 8.0 14.3

  • Načítajme najprv prvé číslo zo súboru, na základe ktorého vieme určiť počet prvkov.
  • Na rozdiel od Javy v C nemožno staticky alokovať pole, ktorého veľkosť je daná premennou.
  • Využime malloc(): dynamicky alokuje priestor v pamäti. ktoré vráti pointer na double
  • Funkcia malloc() sa nachádza v knižnici stdlib.h, nezabudnime #include-núť hlavičkový súbor!
  • po malloc() nezabudnime okamžite napísať aj dealokáciu cez free(), ak už pamäť (pole) nepotrebujeme.
  • V C platí analógia medzi pointrami a poliami:

    • k prvkom mallocnutého poľa pristupujeme rovnako ako k staticky alokovanému poľu (cez indexy [..])
    • parameter funkcie, ktorý je typu pole niečoho, sa v kompilátore prevedie na pointer na niečo. (Vysvetlenie napr. v comp.lang.c FAQ 6.4.)

      • preto môžeme do funkcie, ktorá berie parameter double[], poslať premennú double *.

        double najdi_maximum(double prvky[], int dlzka) {
        
  • Pole alokované malloc()om má nedefinované hodnoty (viď man malloc). Môžu, ale nemusia tam byť nuly. Ak máme namerané len záporné teploty, môže to vracať maximálnu teplotu, ktorá nie je v dátach!

  • Vynulovať alokované pole môžeme funkciou calloc(), ktorá alokuje pamäť a zároveň ju nuluje.

Celý zdroják

#include<stdio.h>
#include<math.h>
#include<limits.h>
#include<stdlib.h>

double najdi_maximum(double prvky[], int dlzka) {
  int i = 0;
  double maximum = INT_MIN;
  while(i < dlzka) {
    if(prvky[i] > maximum) {
      maximum = prvky[i];
    }
    i++;
  }
  return maximum;

}

int main() {
  FILE * subor;
  double pocet_prvkov = 0;
  int i = 0;

  double * prvky;

  subor = fopen("teploty.txt", "r");
  if(subor == NULL) {
    perror("Subor sa nepodarilo otvorit");
    return -1;
  }

  fscanf(subor, "%lf", &pocet_prvkov);

  /* prvky = malloc(pocet_prvkov * sizeof(double)); */
  prvky = calloc(pocet_prvkov, sizeof(double));
  if(prvky == NULL) {
    perror("malloc() zlyhal");
  }

  for(i = 0; i < pocet_prvkov; i++) {
    fscanf(subor, "%lf", &prvky[i]);
  }

  printf("Maximum je: %f", najdi_maximum(prvky, pocet_prvkov));

  free(prvky);
  fclose(subor);
  return 0;
}

Rodné čísla [jednoprechodové, nerealizované na cvičení]

V textovom súbore sa nachádza niekoľko rodných čísiel, pričom prvý riadok súboru obsahuje číslo udávajúce počet údajov v súbore. Pre každé rodné číslo zistite, či je to muž alebo žena. Použite čo najmenej pamäte.

Z každého riadka načítavajme len tretie znaky a podľa nich sa rozhodnúť, či ide o muža alebo ženu.

Môžeme sa spoľahnúť na korektnú štruktúru súboru: na každom riadku je desaťznakové číslo, ukončené znakom nového riadka \n.

V nekonečnom cykle potom načítavajme desať znakov a následne znak konca riadka. Tretí znak z desiatich skontrolujeme na rovnosť s 5 či 6, indikujúcou ženu.

V kóde využijeme

  • fgetc(): funkcia, ktorá vráti celé číslo reprezentujúce znak, alebo špeciálny znak EOF, ak nastane koniec súboru.
  • goto, ktorou môžeme vyskočiť z vnoreného cyklu a doskočiť na riadok označený návestím (labelom), čím trochu sprehľadníme kód.

Zdrojový kód

#include<stdio.h>

int main() {
  int i = 0;
  int znak = 0;
  FILE * subor;

  subor = fopen("rodnecisla.txt", "r");
  if(subor == NULL) {
    perror("Subor sa nepodarilo otvorit");
    return -1;
  }

  while(1) {
    for(i = 0; i < 10 + 1; i++) {
      znak = fgetc(subor);
      if(znak == EOF) {
        goto CLOSE;
      }
      if(i == 2) {
        if(znak == '5' || znak == '6') {
          printf(" Je zena\n");
        } else {
          printf(" Je muz\n");
        }
      }
    }
  }
  CLOSE:
  fclose(subor);

  return 0;
}

Reťazce

Rodné čísla

V textovom súbore sa nachádza niekoľko rodných čísiel, pričom prvý riadok súboru obsahuje číslo udávajúce počet údajov v súbore. Pre každé rodné číslo ho vypíšte spolu s indikátorom, či je to muž alebo žena.

Jedna úvaha je načítavať rodné čísla ako čísla.

  • 1201021234 je 2. január 2012, resp. 1912, čo je 1 201 021 234, teda miliarda
  • 9912319999 je 31. december 1999, čo je 9 912 319 999, teda 9 miliárd. Ak máme dátový typ unsigned long, jeho maximálna hodnota je 4 294 967 296, čo je mimo rozsahu.

Druhá úvaha využíva reťazce.

V C nie je zabudovaný dátový typ pre reťazec / String. Simuluje sa poľom znakov, ktoré je podľa silnej konvencie ukončené znakom s ASCII hodnotou 0, (pozor, nie je to znak '0', tá má iný ASCII kód!), zapisovaným ako \0.

Na načítavanie reťazca použime fscanf().

fscanf(subor, "%10s", riadok);
  • formátovací reťazec musí obsahovať počet znakov, ktoré sa majú načítať. Pre 10 znakov použime %10s.
  • pozor, ak načítavame 10 znakov do buffera (v tomto prípade je ním pole znakov), nezabudnime alokovať jedno miesto pre koncovú nulu!

    char riadok[11]
    
  • ak formátovací reťazec neobsahuje dĺžku, môže veľmi ľahko dôjsť k pretečeniu buffera!

  • pri načítavaní by sa zdalo, že potrebujeme použiť ampersand &, aby sme uviedli adresu v pamäti, kam sa majú zapísať dáta načítané funkciou fscanf. Reťazec je ale pole znakov, ktoré je ekvivalentné s pointerom, ktorý je adresa a tak ampersand nesmieme uviesť. Ak sa pomýlime, chyba bude:

    error: format ‘%10s’ expects type ‘char *’, but argument 3 has type ‘char (*)[11]’
    
  • vysvetlenie ampersandu napríklad v prezentácií zo Systémového programovania 2010.

  • fscanf() pracuje s jednotlivými položkami oddelenými bielym miestom. Prekvapivo by načítala aj takýto súbor:

    1201021234 9912319999 8962011111
    
  • na načítavanie riadkov, ktoré spĺňajú komplexný formát je lepšia fgets(), ktorá je riadkovo orientovaná a rovno vracia reťazec alebo NULL, ak nastane koniec súboru.

Celý kód

#include<stdio.h>

int main() {
  int pocet_prvkov = 0;
  int i = 0;
  FILE * subor;
  char riadok[10 + 1];

  subor = fopen("rodnecisla.txt", "r");
  if(subor == NULL) {
    perror("Subor sa nepodarilo otvorit");
    return -1;
  }

  fscanf(subor, "%d", &pocet_prvkov);
  for(i = 0; i < pocet_prvkov; i++) {

    fscanf(subor, "%10s", &riadok);

    if(riadok[2] == '5' || riadok[2] == '6') {
        printf("Je zena\n");
    } else {
        printf("Je muz\n");
    }
  }

  fclose(subor);
  return 0;
}

Pretečenie buffera [nerealizované na cvičení]

Vytvorte súbor s textom najneobhospodarovatelnejsi, a načítajte ho do desaťznakového buffera. Sledujte pretečenie buffera (buffer overflow).

Vytvorenie súboru:

echo najneobhospodarovatelnejsi > badmalloc.txt

Ďalšie dlhé slová možno nájsť v blogu na SME.

Výsledný zdroják

#include <stdio.h>

int main() {
        char buffer[11];
        FILE * file;

        file = fopen("badmalloc.txt", "r");
        if(file == NULL) {
                perror("Cannot open file");
                return -1;
        }

        fscanf(file, "%s", buffer);

        printf("%s\n", buffer);

        fclose(file);

        return 0;
}

Program sa môže, ale nemusí správať korektne. Kratšie riadky v súbore sa môžu pokojne načítať, aj vypísať a zdanlivo bude všetko v poriadku, ale na Debian Linuxe riadky dvakrát (!) dlhšie než buffer vytvárajú SEGMENTATION FAULT, teda prístup do cudzej pamäte.

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *