[Cvičení 4] [Obsah] [Cvičení 6]

Cvičení 5


Témata


Dynamická alokace (dokončení)

Úloha 5.1

Napište program, který načítá z klávesnice posloupnost celých čísel zakončených -1 (hodnota -1 se již do pole neukládá, velikost pole není předem známa). Program alokuje na počátku dynamicky pole určité velikosti. Pokud velikost pole v průběhu načítání nestačí, program provede realokaci. Použijte pouze funkci malloc, realokaci naprogramujte kompletně, zatím bez využití funkce realloc. Pro kopírování pole využijte funkci memcpy: void *memcpy(void *cil, const void *zdroj, size_t n); z knihovny mem.h.

Řešení:

Dev C++:dyn_pole2.dev, dyn_pole2.c
CodeBlocks:dyn_pole2.cbp, dyn_pole2.c
Dev C++:dyn_pole2b.dev, dyn_pole2b.c
CodeBlocks:dyn_pole2b.cbp, dyn_pole2b.c

Úloha 5.2

Přepište program z úlohy 5.1 za použití funkce realloc. Před prvním voláním funkce nezapomeňte přiřadit do ukazatele na pole hodnotu NULL.

Řešení:

Dev C++:dyn_pole3.dev, dyn_pole3.c
CodeBlocks:dyn_pole3.cbp, dyn_pole3.c

Ukazatelová aritmetika

S hodnotami ukazatelů je možné v jazyce C provádět aritmetické operace. Je možné přičítat/odečítat celočíselnou hodnotu, která představuje posuv po buňkách paměti (prvcích pole). Dále je možné hodnoty ukazatelů porovnávat a odečítat.

Mějme např. následující deklarace a dynamicky alokované pole o velikosti 10 prvků typu int :

  int *pole;
  int *uk;
  
  pole = (int *)malloc(sizeof(int)*10);
  uk = pole+5;
Ukazatel pole odkazuje na počátek dynamicky alokované paměti. Hodnotou výrazu pole + 2 je ukazatel na třetí prvek pole, tj. prvek s indexem 2 (obr. 1). Neznamená to však, že ukazatel pole + 2 ukazuje na buňku paměti, která má adresu vyšší o hodnotu dva. Při ukazatelové aritmetice se bere v úvahu velikost prvků, na které ukazatel ukazuje. Předpokládejme, že sizeof(int)=4, sizeof(char)=1 a že paměť je organizována (adresována) po slabikách - bytech (jak to ostatně dodnes počítače PC mají). Ukazatel pole+2 tedy ukazuje na adresu zvětšenou o 8, tedy na adresu ((char*)pole)+2*sizeof(int).

Ukazatelová aritmetika
Obrázek 1: Ukazatelová aritmetika

Do ukazatele uk jsme přiřadili hodnotu ukazatele pole+5, uk tedy ukazuje na šestý prvek pole a ukazatel uk+2 ukazuje na osmý prvek pole. Pokud bychom do kódu následně zapsali porovnávací výraz uk == pole+5, jeho hodnota by byla nenulová (pravda).
Přičtení záporné hodnoty k ukazateli představuje analogicky posuv „vpřed“, např. ukazatel pole-1 ukazuje na oblast paměti, která k poli již nepatří. Protože jazyk C nekontroluje meze polí, můžeme tento výraz v programu napsat (pokud budeme manipulovat s daty na této adrese, přepíšeme zpravidla hodnoty jiných proměnných).

Přístup k datům pomocí ukazatelů, statická versus dynamická pole

Z předešlé kapitoly víme, že přístup k hodnotě, na kterou ukazatel ukazuje, se provádí pomocí hvězdičky. Prvkům pole, které jsou označeny ukazateli na obr. 1, přiřadíme hodnoty tímto kódem:
  *pole = 2;
  *(pole+2) = 10;
  *uk= -4; // zde je možné napsat též *(uk+0) = -4;
  *(uk+2) = 5;
Zde je důležité poznamenat, že přístup k prvkům statického a dynamického pole je naprosto stejný. Následující dva zápisy jsou v jazyce C zcela ekvivalentní, je jedno, který z nich v programu použijeme:
   *(pole+2) = 10;
   pole[2] = 10;
Pokud máme deklarované statické pole, např. int A[30], A je konstatní ukazatel na počátek pole, lze tedy psát *(A+20)=10 a tento zápis je ekvivalentní zápisu A[20]=10. Do samotné proměnné A nelze přiřadit žádnou hodnotu, neboť jde o konstatní ukazatel inicializovaný při překladu (překladač hlásí chybu).

Ukazatelová aritmetika se efektivně využívá ve funkci scanf, kde se jako skutečné parametry uvádějí adresy paměti, na které se má uložit zkonvertovaná hodnota. Místo scanf("%d",&a[i]); je lépe psát scanf("%d",a+i);.

Úloha 5.3

Napište program, který vypíše v desítkové a šestnáctkové soustavě zadané číslo typu int. Dále program vypíše v šestnáctkové a desítkové soustavě jednotlivé slabiky (byty) zadaného čísla.
(Návod: Využijte ukazatele na int a ukazatelové aritmetiky, přetypování ukazatele na typ (unsigned char*), obr. 2.)

Přetypování ukazatelů
Obrázek 2: Přetypování ukazatelů

Řešení:

Chybné řešení: Dev C++: vypis_slabik1.dev, vypis_slabik1.c
  CodeBlocks:vypis_slabik1.cbp, vypis_slabik1.c
Správné řešení: Dev C++: vypis_slabik2.dev, vypis_slabik2.c
  CodeBlocks:vypis_slabik2.cbp, vypis_slabik2.c
Předkládáme dvě řešení, první z nich je záměrně napsané špatně (nechť si čtenář zkusí zadat hodnotu např. 1256). Příčina chybného výpisu výsledku tkví v tom, že typ char je uvažován se znaménkem. Při zadání čísla 1256 má slabika nejnižšího řádu hodnotu 0xE8, tedy 7. bit má hodnotu 1. Ve formátovacím řetězci je požadavek na tisk hodnoty celého čísla (4 slabiky) v šestnáctkové soustavě %X, Velikost dat dereferencovaných *((char*)p+i) je přirozeně 1 slabika, jde o přetypování na ukazatel na typ char. Před předáním této slabiky funkci printf se automaticky provede roztažení na velikost 4 slabiky. Protože je typ char uvažován se znaménkem a nejvyšší bit slabiky 0xE8 má hodnotu 1, což je v reprezentaci čísel v doplňkovém kódu znaménkový bit, je považována hodnota 0xE8 za záporné číslo. Roztažení na 32 bitů je provedeno podle pravidel pro záporná čísla v doplňkovém kódu, tedy všechny bity vyšších řádů jsou nastaveny na hodnotu 1. Musíme přetypovat ukazatel nikoliv na typ char, ale na unsigned char.

(Poznámka: tento program bude tisknout na různých počítačích různé výstupy; jednak se u překladačů liší velikost reprezentace typu int, jednak každý procesor ukládá data do paměti jiným způsobem - slabiky nižšího řádu na nižší adresy (little endian, např.Intel) nebo opačně, slabiky vyššího řádu na nižší adresy (big endian, např. Motorola).

Využití operace rozdílu ukazatelů

Ukazatele je možné také odečítat, smysluplné je provádět rozdíl s ukazateli, kteří ukazují na stejný typ. Sčítat je nemá smysl, výsledkem by byla nesmyslná adresa. Velikost rozdílu ukazatelů je počet položek, na které ukazatelé ukazují. Předpokládejme opět, že sizeof(int)=4. Mějme následující fragment kódu:
  int *p = (int*)malloc(sizeof(int)*10);
  int *k;
  k = p + 5;
Hodnota rozdílu (k-p) je 5, tedy ((char*)k-(char*)p)/sizeof(int).

Na závěr ještě ukážeme, jak provést výpis prvků pole pouze s využitím ukazatelů, ukazatelové aritmetiky a porovnání ukazatelů. Výpis pozice hledaného prvku je proveden s využitím rozdílu ukazatelů:

  int A[5] = {1,2,3,4,5};
  int x;
  int *p,
  int *za_konec;

  za_konec = A+5;
  for(p=A;p<za_konec;p++) printf("%d ",*p);

  printf("Zadej hledane cislo: ");
  scanf("%d",&x);

  for(p=A;p<za_konec && *p != x;p++) ;  //strednik ma vyznam prazdneho prikazu
  if (p==za_konec) 
    printf("Cislo nenalezeno");
  else
    printf("Cislo je na pozici %d\n",p-A+1);
 
Kód je k dispozici ke stažení:
Dev C++: priklad_ukaz.dev, priklad_ukaz.c
CodeBlocks: priklad_ukaz.cbp, priklad_ukaz.c

Úloha 5.4

Napište program, který vypíše pět posledních prvků pole několika způsoby. Využijte právě různé způsoby přístupu k prvkům pole.

Řešení:

Dev C++:vypis_pole.dev, vypis_pole.c
CodeBlocks:vypis_pole.cbp, vypis_pole.c


[Cvičení 4] [Obsah] [Cvičení 6]