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- Vysvetlenie je v prezentácii o smerníkoch.
- Funkcia
malloc()
sa nachádza v knižnicistdlib.h
, nezabudnime#include
-núť hlavičkový súbor! - po
malloc()
nezabudnime okamžite napísať aj dealokáciu cezfree()
, 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) {
- k prvkom mallocnutého poľa pristupujeme rovnako ako k staticky alokovanému poľu (cez indexy
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 znakEOF
, 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é funkcioufscanf
. 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 aleboNULL
, 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.