Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info

Ciele cvičenia

Cvičenie je zamerané na dynamickú alokáciu pamäte a prácu s nástrojom valgrind. 

Po osvojení si učiva, si študent prehĺbi vedomosti a zručnosti s prácou so smerníkom a pamäťou, dokáže dynamicky alokovať pamäť mimo hlavnú funkciu main a dokáže použiť nástroj valgrind na kontrolu správneho manažmentu pamäte.

  • Dynamická alokácia (malloc, calloc a free)
  • Valgrind
Info

Odporúčaná literatúra a dôležité odkazy

[1] KPI-FEI-TUKE: Prednáška č. 2

[2] KPI-FEI-TUKE: Cvičenie č. 4 -Buffer Overflow, Memory Leaks and Valgrind

[3] Valgrind - Memcheck: a memory error detector

[4] Tutorialspoint: C Library - <stdlib.h>



Dynamická alokácia pamäte

O dynamickej alokácií  pamäte bolo veľa povedané na 2. prednáške,  ktorú vám touto cestou dôrazne odporúčam pozrieť [1]! Pre potreby cvičenia, je možné uviesť niektoré základné fakty, ktoré by ste si mali osvojiť.  

Pri riešení väčšiny úloh, sme si vystačili so statickou alokáciou pamäte. To znamená, že vždy bolo jasné koľko pamäte budeme potrebovať a túto sme alokovali. Napríklad pri deklarácii 1R poľa alebo reťazca sa to urobilo napríklad takto:

char pole[100];

Nevýhodou tohto spôsobu bolo to, že takto alokované miesto v pamäti je možné použiť iba vo vnútri funkcie, v ktorej bola premenná deklarovaná. Výnimkou je, ak sa deklarácia vykoná napr. vo funkcii main() a následne sa inej funkcii posunie iba smerník na túto premennú. Nefunguje to ale opačne. Ak sa premenná deklaruje vo volanej funkcii, po jej ukončení, toto miesto v pamäti neostáva alokované.  

Statická alokácia má tiež nevýhodu v tom, že je alokované stále rovnaké množstvo pamäte aj v prípade, že ju nepotrebujeme. Napr. char pole[100], bude bez ohľadu na to, čo do neho uložíme, vždy zaberať 100 bajtov. 

Tieto nevýhody odstraňuje dynamická alokácia, ktorá umožňuje

  • Efektívne využívať pamäť
  • Alokovať miesto v pamäti aj z volaných funkcií

Najčastejšie sa stretávame s príkazmi malloc(), calloc(), free() a realloc(). Tieto sú z knižnice stdlib.h. Viac sa môžete dočítať na [1] a [4].

malloc() 

Miesto alokujeme napríklad takto:

 char* str = malloc((dlzka_retazca+1)*sizeof(char)); 

Takto zadaným príkazom sa v pamäti alokuje toľko miesta, koľko je potrebné pre reťazec a jeho terminátor, ktorého dĺžka je uložená v premennej dlzka_retazcaFunkcia vráti smerník na začiatok tohto alokovaného miesta a v prípade chyby vráti NULL. Malloc sa nestará o to čo bolo v pamäti predtým!


calloc()

Funguje podobne ako malloc ale pamäť inicializuje hodnotou '\0'. Použijeme ho napr. takto:  

char* str =  calloc(dlzka_retazca+1, sizeof(char));


free()

Pred ukončením programu, alebo ak už pamäť nepotrebujeme, je potrebné ju uvoľniť! Môžeme to urobiť takto:

free(str)



Úloha 1.

Napíšte vlastnú funkciu s názvom StrCat, ktorá spojí dva reťazce do jedného. 

  • Návratová hodnota bude smerník na reťazec
  • Vstupné parametre budú typu: const char*
  • Pamäť kde bude uložený výsledný reťazec bude alokovaná dynamicky a v tele funkcie StrCat.
  • Funkcia môže byť definovaná v súbore .c spolu s funkciou main.
  • Správnosť riešenia overte s nástrojom valgrind a prípadné chyby odstráňte.
  • Funkcia bude skompilovaná takto:


Code Block
languagebash
gcc -fno-stack-protector -g -Werror -Wall dynamin.c -lm -o DYNAMIC


Po kompilácií by sme mali vidieť nasledovné:

Code Block
languagebash
ab123cdd@zapfei:$ ./DYNAMIC

 Retazec 1. >> Ahoj
 Retazec 2. >> Svet!

 Spojenie retazcov >>Ahoj Svet!


Zdá sa, že program funguje správne, ale nie je tomu tak!

Code Block
languagecpp
titledynamic.c
linenumberstrue
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* StrCat(const char* ret_1,const char* ret_2);
int main()
{
  char ret_1[] = "Ahoj";
  char ret_2[] = " Svet!";
  char* str;
  printf("\n Retazec 1. >> %s\n Retazec 2. >>%s\n",ret_1,ret_2);
  str = StrCat(ret_1, ret_2); 
  printf("\n Spojenie retazcov >>%s\n\n",str);
  return 0;
}
char* StrCat(const char* ret_1,const char* ret_2)
{
  int L1 = strlen(ret_1);
  int L2 = strlen(ret_2);  
  char* str = malloc((L1+L2+1)*sizeof(char));
  for (int i=0; i<L1+L2; i++)
  {
    if(i<L1)
      str[i] = ret_1[i];
    else
      str[i] = ret_2[i-L1];   
  }
  str[L1+L2+1] = '\0';
  return str;
}

Valgrind

Tento nástroj slúži na identifikáciu únikov a iných problémov spojených so správou pamäte. Možné chybové hlásenia a ich vysvetlenie je popísané napr. v [3]. 


Ak program spustíme s nástrojom valgrind zobrazia minimálne 3 chyby! Výpis vpravo --->

  • ==208355== Invalid write of size 1
  • ==208355== Conditional jump or move depends on uninitialised value(s)
  • ==208355== total heap usage: 2 allocs, 1 frees, 1,035 bytes allocated


Nakoľko na cvičení riešia problémy študenti a nie vyučujúci, je potrebné, aby ste v kóde našli chyby a opravili ich.

Pomôcka: 

  • Zapisujeme/čítame tam kde  máme premennú alokovanú?
  • Upratali sme po sebe v pamäti?


Po odstránení chýb by kontrola cez valgrind mala vyzerať nasledovne:

Code Block
languagebash
ab123cdd@zapfei:$ valgrind ./DYNAMIC
==210825== Memcheck, a memory error detector
==210825== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==210825== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==210825== Command: ./DYNAMIC
==210825==

 Retazec 1. >> Ahoj
 Retazec 2. >> Svet!

 Spojenie retazcov >>Ahoj Svet!

==210825==
==210825== HEAP SUMMARY:
==210825==     in use at exit: 0 bytes in 0 blocks
==210825==   total heap usage: 2 allocs, 2 frees, 1,035 bytes allocated
==210825==
==210825== All heap blocks were freed -- no leaks are possible
==210825==
==210825== For lists of detected and suppressed errors, rerun with: -s
==210825== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Code Block
languagebash
ab123cdd@zapfei:$ valgrind ./DYNAMIC
==208355== Memcheck, a memory error detector
==208355== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==208355== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==208355== Command: ./DYNAMIC
==208355==

 Retazec 1. >> Ahoj
 Retazec 2. >> Svet!
==208355== Invalid write of size 1
==208355==    at 0x109308: StrCat (dynalloc.c:39)
==208355==    by 0x10921F: main (dynalloc.c:16)
==208355==  Address 0x4a4748b is 0 bytes after a block of size 11 alloc'd
==208355==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==208355==    by 0x109289: StrCat (dynalloc.c:30)
==208355==    by 0x10921F: main (dynalloc.c:16)
==208355==
==208355== Conditional jump or move depends on uninitialised value(s)
==208355==    at 0x483EFB8: __strlen_sse2 (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==208355==    by 0x48CDE94: __vfprintf_internal (vfprintf-internal.c:1688)
==208355==    by 0x48B6EBE: printf (printf.c:33)
==208355==    by 0x10923B: main (dynalloc.c:17)
==208355==
 Spojenie retazcov >>Ahoj Svet!

==208355== HEAP SUMMARY:
==208355==     in use at exit: 11 bytes in 1 blocks
==208355==   total heap usage: 2 allocs, 1 frees, 1,035 bytes allocated
==208355==
==208355== LEAK SUMMARY:
==208355==    definitely lost: 11 bytes in 1 blocks
==208355==    indirectly lost: 0 bytes in 0 blocks
==208355==      possibly lost: 0 bytes in 0 blocks
==208355==    still reachable: 0 bytes in 0 blocks
==208355==         suppressed: 0 bytes in 0 blocks
==208355== Rerun with --leak-check=full to see details of leaked memory
==208355==
==208355== Use --track-origins=yes to see where uninitialised values come from
==208355== For lists of detected and suppressed errors, rerun with: -s
==208355== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Úloha 1.1

Doplňte svoje riešenie o zadávanie vstupných reťazcov pomocou funkcie Nacitaj(). Táto funkcia nebude mať žiadny vstupný parameter a jej návratová hodnota bude smerník na načítaný reťazec. Pre načítanie reťazca použite staticky alokované 1R pole s názvom Buffer a reťazec, na ktorý bude vrátený smerník bude alokovaný dynamicky so zreteľom na efektívne využitie pamäte. Svoj kód overte cez valgrind!


Code Block
languagecpp
linenumberstrue
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* StrCat(const char* ret_1,const char* ret_2);
char* Nacitaj(); // deklarácia funkcie Nacitaj
int main()
{
  char* ret_1 = Nacitaj();
  char* ret_2 = Nacitaj();
  char* str;
// pokračovanie je rovnaké ako v úlohe 1