Mala šola C jezika - Kazalci

Vse o programiranju na in za PC

Moderatorji: Kroko, tilz0R

Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 24 Sep 2016, 20:06

Z današnjim dnem (oz. tem momentom) se začenja mala šola C jezika, natančneje tema glede kazalcev, ki je precej zahtevna za razumevanje.

Za začetek naj omenim, da sem besedo "kazalec" uporabil zadnjič. Od sedaj naprej bo to "pointer", pa čeprav to ni slovenska beseda!

Predviden obseg male šole:
  • Uvod v pointerje, kaj so, zakaj jih uporabljati in kako se jih deklarira v kodi
  • Tipi kazalcev, kaj pomenijo sami tipi pri pointerjih.
  • Aritmetične operacije z pointerji
  • Pointerji in polja spremenljivk (array-i)
  • Pointerji ter uporaba kot parameter pri funkcijah in uporaba s strukturami
  • Void tip pointerja in casting pointerjev (sprememba tipa pointerja) med uporabo
  • Pointer na pointer in pomen ter uporaba
  • Pointerji v uporabi s stringi
  • Pointerji kot primer kvazi objektnega programiranja v C jeziku.
Mogoče sem še kaj pozabil in bom sproti dodal.

Potrebna predznanja:
  • Razumevanje osnovnih podatkovnih tipov in struktur ter koliko zasedejo pomnilnika.
  • Razumevanje, kako se ustvari spremenljivko, ki je polje (ang. array).
  • Celoten vodič temelji na dejstvu, da imamo opravka z 32-bitno arhitekturo, ki ima 32-bitni naslovni prostor.

Pravila v šoli
  • Jaz govorim, vi poslušate
  • Če česa ne razumete, se tam vstavimo in razčistimo zadeve. Zahtevam vaša vprašanja. Če je koga sram, naj za to uporabi privatno sporočilo, jaz bom pa objavil vprašanje in odgovor. Tako avtor ne bo znan ostalim.
  • Pomembno je, da ne nadaljujemo na naslednje poglavje, dokler ne razumete vsi trenutnega.
  • Delate domače naloge, ki se bodo pojavljale. Ni nujno, da bo vsako poglavje imeli tudi domačo nalogo. Pri vsakem velikem naslovu (poglavju) bo tudi zraven domača naloga pisala. Mogoče bo kdaj pisalo "danes ni domače naloge", ampak potrebno je pogledati ta del na koncu.
  • Ta tema bo vedno zaklenjena, vendar se bo nova vsebina dodajala tukaj. Sproti bom ustvaril tudi temo za rešitve domačih nalog ter debate in vprašanja. Spodaj v temi imate opcijo, da se naročite na nove prispevke.
  • Vsa vprašanja (upam, da jih bo res veliko) bodo napisana in odgovorjena v tej temi, glede na poglavje kamor spada vprašanje.

Programsko okolje
Sam bom kot programsko okolje uporabljal Visual Studio. Tisti, ki nimate na PC okolja za programiranje C za PC, si lahko pogledate tutorial na tem forumu, kako mingw usposobit ter GCC. Napisal ga je uporabnik Kroko.
Brezplačna verzija Visual studia

O C-ju nimam pojma. Kako začeti?
Kroko ima začetni tečaj za C.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 24 Sep 2016, 20:16

1. Uvod
Kazalci (v nadaljevanju »pointerji«, ang. »pointers«) so spremenljivke, ki imajo zapisano lokacijo, kje v pomnilniku se nahaja podatek.
Glede na ostale spremenljivke, je potrebno pointer spremenljivko deklarirati z dodatkom zvezdice.

Koda: Izberi vse
//Vsi tri zapisi so pravilni, kako boš uporabljal/a, se sam odločiš.
tip_pointerja * ime_spremenljivke;
tip_pointerja* ime_spremenljivke;
tip_pointerja *ime_spremenljivke;

Kjer velja:

  • tip_pointerja predstavlja podatkovni tip, int, char, long, short, float, double, long long. Tip je lahko tudi struktura, kar bo prikazano v nadaljevanju.
  • * (zvezdica) pomeni, da bo naša spremenljivka le kazalec in bo hranila le lokacijo v pomnilniku.
  • ime_spremenljivke predstavlja samo ime kazalca.

Primeri:
Koda: Izberi vse
int* int_ptr;
long* long_ptr;
char* char_ptr;
float* float_ptr;
double* double_ptr;

void* void_ptr; //Poseben primer, ki nima znanega tipa podatka, ampak samo lokacijo.


2. Zakaj bi sploh nekdo uporabljal pointerje?

Vsak se ponavadi najprej vpraša zakaj* Tudi vsak, ki je kadarkoli delal kakšen program z MCU-jem, jih je že uporabljal.
Primer:
Če želimo postaviti izhodni pin na 1, je potrebno v register, ki je temu namenjen, postaviti primeren bit na 1.
Da to lahko storimo, moramo najprej poznati lokacijo, kje v spominu se nahaja ta register.
Ko to izvemo, pišemo na tisto lokacijo z uporabo pointerja na takšen ali drugačen način.

3. Koliko bytov je velikost pointerja

Za začetek, da se osveži spomin, ponovim koliko bytov je velikost posameznega podatkovnega tipa (pomembno je, da vemo velikosti, saj bomo to kasneje potrebovali pri uporabi!):

  • char: 1-byte
  • short: 2-byta
  • int: odvisno od arhitekture, ampak ta vodič predvideva, da smo na 32-bitni arhitekturi (recimo ARM), to predstavljajo 4-byti
  • long: 4-byti
  • long long: 8-bytov
  • float: 4-byti
  • double: 8-bytov

Tako, osnovni tipi so našteti. Če prebereš prvi stavek tega vodiča, kaj je to pointer, je zapisano, da on le shranjuje lokacijo v pomnilniku.
Iz tega zapisa, sledi, da vsi pointerji, ne glede na tip, uporabljajo isto velikost pomnilnika, saj shranjujejo lokacijo, ki je lahko poljubna znotraj naslovnega prostora procesorja.
Predvidevajmo, da je arhitektura ARM, ki je 32-biten in ima 32-bitni naslovni prostor (4G x po 1Byte možnega spomina za celoten sistem). To pomeni, da je vsak pointer dolžine 4-bytov.
Z drugimi besedami, to pomeni, da bomo porabili 4-byte RAM-a, ko bomo uporabili karkoli izmed spodnjih opcij:

Koda: Izberi vse
//Vsak spodnji ukaz porabi 4-byte RAM-a v pomnilniku, ne glede na to, kakšen tip predstavlja pointer.
int* int_ptr;
long* long_ptr;
char* char_ptr;
float* float_ptr;
double* double_ptr;
void* void_ptr;


Zakaj je tako?
Potrebno je vedeti, da če imamo recimo pointer na en znak (char), ki je sam po sebi velik 1 byte (samo en znak), se le-ta lahko nahaja v pomnilniku na lokaciji 0x12345678, za kar potrebujemo 4-byte veliko spremenljivko, če želimo shraniti celoten naslov. To počne char* spremenljivka, ki je velika 4-byte.

Za dokazila, da je to res, lahko uporabimo ukaz sizeof v C-ju, ki vrne velikost parametra v byte enotah.

Primer:
Koda: Izberi vse
printf("Size of char: %d", sizeof(char));
printf("Size of char pointer: %d", sizeof(char *));

//Zgornja koda izpiše:
Size of char: 1
Size of char pointer: 4

Glede na to, da so vsi pointerji isto veliki, ker je naslovni prostor fiksno definiran, čemu potem sploh služi tip pointerja?
Tip pointerja nam pa pove, kakšen tip podatka pričakujemo na lokaciji, katero imamo shranjeno v pointer spremenljivki. Več o tem v naslednjem poglavju.

Domača naloga 1: Napišite program, ki z uporabo sizeof ukaza v C-ju vrne velikost podatkovnih tipov ter pointerjev istega tipa. Preverite to za osnovne tipe v C jeziku. Uporabite isti primer, kot sem ga zgoraj. Rešitve (print screen teksta v konzoli) napišite v temo, ki je namenjena debati ter vprašanjem. Za izpis teksta v konzolo lahko uporabite funkcijo printf.

Če bo sizeof vrnil, da je pointer spremenljivka velika 8-bytov, ne se ustrašit. To pomeni, da uporabljate sistem (64-biten), ki uporablja 8-bytni naslovni prostor.
Če se vam to zgodi, bo to potrebno upoštevati v nadaljevanju te šole.


Tema za debato.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 25 Sep 2016, 20:29

4. Pomen tipa pointerja

Ko izberemo tip pointerja (int *, float *, char *, etc) povemo, kakšen tip podatka pričakujemo na lokaciji, ki je shranjena v pointerju.

Z izjemo tipa void * vsi tipi že predstavljajo tip za prevajalnik, ki ve kako prebrati podatek na lokaciji v določenemu trenutku.
Void* je generičen tip, ki samo pove, da gre za pointer, ne pove pa kakšen tip podatka je na lokaciji, kamor kaže pointer. Tip določimo šele ob branju ali pisanju na lokacijo, ki jo ta pointer shranjuje. Več o tem v nadaljevanju.

Tip pointerja z domačimi besedami predstavlja število bytov, ki se bodo prebrali iz lokacije, kamor kaže naš pointer. Primer kaže slika spodaj:

Memory.png
Primer pomnilnika


Razlaga slike:
Imamo 16-bytov v našem pomnilniku, ki se začne na lokaciji 0x1000. To lokacijo beremo z dvemi tipi pointerja, prvi je char * (rdeča), drugi je int * (zelena).
Vsak pomnilnik ima vsak byte na svoji lokaciji, kar je pomembno, da se zavedamo, pri nadaljevanju te razlage.
Char kot takšen (brez pointerja) zavzame 1-byte pomnilnika, int pa 4-byte. Število elementov glede na tip pointerja na lokaciji lahko potem določimo tako:
Koda: Izberi vse
st_elementov = celoten_pomnilnik_v_bytih / velikost_tipa_pointerja_ce_ni_pointer


Zgornja enačba je grda na oko, zato pokažem primer za integer in char:
Koda: Izberi vse
//Imamo pomnilnik z 16 byti
//Imamo char tip pointerja (char = 1 byte) in int tip pointerja (int = 4 byte)

//Koliko char-jev imamo v našem pomnilniku?
st_charjev = 16 / 1 = 16 elementov

//Koliko integerjev imamo v našem pomnilniku?
st_integerjev = 16 / 4 = 4 elementi


Glede na rešitve, lahko sklepamo, da če bomo brali/pisali kot char, bomo vsak byte posebej urejali in bomo za to potrebovali 16x narediti, medtem, ko branje/pisanje kot int * pomeni le 4 elemente, posledično pomeni, da smo z enim int * branjem prebrali 4 byte iz pomnilnika naenkrat.

Tip pointerja ima pomen tudi pri aritmetični operacijah, ki pa si jih bomo pogledali kasneje.


5. Pridobitev lokacije pointerja

V tem razdelku bom predstavil, kako ugotovimo lokacijo naših podatkov v pomnilniku.
Za primer registrov za ledice je potrebno odpreti podatkovni list MCU-ja, v osnovi pa za to obstaja poseben znak & (AND), ki vrne lokacijo spremenljivke v pomnilniku.

Primer:
Koda: Izberi vse
int number = 5; //Vrednost naše spremenljivke je 5. Lokacija v pomnilniku ni znana, vemo samo vrednost spremenljivke.
int* ptr = &number; //Ustvarili smo pointer ptr, ki vsebuje lokacijo spremenljivke number. Za to poskrbi AND znak, ki vrne lokacijo spremenljivke number.


Zgornji primer prikazuje pridobitev lokacije, če imamo spremenljivko, ki ni polje elementov.
Kadar je spremenljivka polje elementov, se lokacija skriva v spremenljivki sami.

Primer:
Koda: Izberi vse
int numbers[3] = {10, 20, 30}; //Ustvarili smo polje treh števil, shranjenih v spremenljivki numbers, ki je polje (array)
int *ptr, *ptr2; //Deklaracija pointerja

//Vrednosti numbers dobimo tako:
printf("Prva vrednost: %d", numbers[0]);
printf("Druga vrednost: %d", numbers[1]);
printf("Tretja vrednost: %d", numbers[2]);

//Shranimo si lokacijo numbers polja
ptr = numbers; //Ker je spremenljivka numbers polje, nam njeno branje predstavlja lokacijo v pomnilniku, znak & ni potreben.

//Drugi način branja lokacije polja je sledeč:
ptr2 = &numbers[0] //numbers[0] predstavlja vrednost. Če dodamo & znak spredaj, dobimo lokacijo, kjer se ta vrednost nahaja.


Zgornji primer nam prikazuje, da je naslov prvega elementa v polju dejansko enak samemu array-u, če preberemo samo spremenljivko.

6. Branje/pisanje z pointerji

Lokacijo podatkov smo pridobili. Sedaj bi radi pisali ali brali na lokacijo z uporabo pointerja.
Za branje/pisanje se uporablja pred pointerjem zvezdica, ki predstavlja pisanje/branje na/iz lokacijo/e, ki jo kaže pointer.

Primer:
Koda: Izberi vse
int number = 5;     //Ustvari spremenljivko number
int* ptr = &number; //Ustvari pointer tipa int, ki kaže na lokacijo spremenljivke number

int prebrano;       //Tukaj bomo shranili prebrano vrednost iz lokacije ptr spremenljivke

//Vrni vrednost, ki je na lokaciji, kamor kaže ptr spremenljivka = vrednost spremenljivke number, ki je 5;
//Vrednost se prekopira. Če se *ptr spremeni, se vrednost v prebrano ne bo spremenila!
prebrano = *ptr;

//Izpiši vrednost
printf("Vrednost number: %d", number);
printf("Vrednost prebrano: %d", prebrano);

//Zapiši novo vrednost na lokacijo, ki jo kaže ptr
*ptr = 10;
//Od tukaj naprej ima spremenljivka number vrednost 10, saj smo z pointerjem, ki kaže na lokacijo spremenljivke number zapisali novo vrednost

printf("Vrednost number: %d", number);
printf("Vrednost prebrano: %d", prebrano);

//Zgornji printf-ji izpišejo:
Vrednost number: 5
Vrednost prebrano: 5
Vrednost number: 10
Vrednost prebrano: 5


Primer pokaže, kako lahko iz nekje drugje (remote) spreminjamo vrednosti naše spremenljivke.

Domača naloga 2: Ustvarite tri spremenljivke, a = 32, b = 27 in rezultat. S pomočjo pointerjev preberite a in b spremenljivki ter jih zmnožite. Rezultat zapišite s pointerjem v spremenljivko rezultat. Vse tri vrednosti izpišite na zaslon.
Rešitve mi pošljite na PM, da ste nalogo opravili pa napišite v to temo V njo napišite tudi vsa morebitna vprašanja in nejasnosti. Prej se zjasnimo, boljše bo!

V naslednjem poglavju bo govora o aritmetičnih operacijah pointerjev ter implicitnemu castingu pointerjev, zato je pomembno, da zgornji del razumete.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 30 Sep 2016, 10:12

7. Aritmetične operacije z pointerji

Tako kot vsaka spremenljivka, je tudi pointer spremenljivka, ki ima vrednost. V tem primeru ta vrednost uporabniku predstavlja lokacijo, na kateri ga zanimajo podatki.
V spodnjih temah bom pokazal, na kaj moramo biti pozorni pri aritmetičnih operacijah, kot so povečevanje vrednosti pointerja, zmanjševanje vrednosti.
Pojasnil bom, zakaj je to dobro, da je tako in kako to prednost uporabiti pri poljih (array-ih).

7.1. Povečanje/Zmanjšanje pointerja

Izreden pomen imajo tipi pointerjev, ko pridemo do situacije, kjer moramo narediti
Koda: Izberi vse
ptr++

ali
Koda: Izberi vse
ptr--

ukaza.
Ta ukaz spremeni vrednost pointerja (vrednost lokacije, kamor kaže, in ne vrednost NA lokaciji, kamor kaže).
S tem ukazom pointer zamenja lokacijo kazanja.

Pri pointerjih, se vsaka zgornja operacija ne poveča za 1 več/manj, pač pa se poveča za toliko, kot je velikost tipa, ki je deklariran kot pointer

Primer:
Koda: Izberi vse
int* ptr_int = 10;   //Postavimo pointer na lokacijo 10, pointer je tipa int. int predstavlja 4-byte
char* ptr_char = 10; //Postavimo tudi drugi pointer na lokacijo 10, pointer je tipa char. char predstavlja 1-byte

//Povečajmo oba pointerja:
ptr_int++;
ptr_char++;

//Izpišimo nove vrednosti:
printf("Vrednost ptr_int: %d", ptr_int);
printf("Vrednost ptr_char: %d", ptr_char);

//Zgornja koda izpiše:
Vrednost ptr_int: 14
Vrednost ptr_int: 11

Kot vidimo, se je pointer, ki je bil tipa int povečal za 4, medtem, ko se je pointer tipa char povečal le za 1 byte. To je zato, ker je int 4-byte velik, medtem ko je char le 1-byte velik tip podatka.

7.2. Uporaba pri poljih

V poglavju 4 sem predstavil, kako izgleda blok podatkov v pomnilniku ter kako poteka branje.
Tokrat bom ponovno predstavil blok podatkov, le da bo ta blok narejen z uporabo spremenljivke, ki je polje.

Spremenljivko deklariramo kot:
Koda: Izberi vse
int polje[10]; //Ustvarimo spremenljivko polje z 10 integer vrednostmi = skupaj zasedeno 40-bytov pomnilnika


Sedaj pa bomo z uporabo aritmetične operacije pointerja prebrali vse vrednosti iz tega array-a, in jih izpisali na zaslon:
Koda: Izberi vse
int polje[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; //Ustvari polje 10 integerjev, ki imajo vrednosti od 1 do 10
int *ptr;

//Shranimo si število elementov našega polja.
int count;

//Oboje je pravilno. Sam se poslužujem uporabe prvega
ptr = polje; //ustvari pointer, ki kaže na lokacijo prvega elementa polja. Ukaz je enak spodnjemu, &polje[0];
ptr = &polje[0]; //Zapomni si, polje[0] pomeni vrednost prvega elementa v polju. Če temu dodamo še & spredaj, povemo kje v pomnilniku se nahaja ta vrednost.

             //Če bi nas naprimer zanimala lokacija polje[3], bi uporabili &polje[3];

             //Sedaj pa izpišimo vse elemente na zaslon.
             //Najprej moramo vedeti, koliko elementov ima naše polje sploh.
count = sizeof(polje) / sizeof(int); //Izračunamo število elementov polja.

 //sizeof(polje) vrne število v bytih, ki jih spremenljivka zasede. V našem primeru je to 40
 //sizeof(int) vrne velikost int podatkovnega tipa. V našem primeru je to 4. 40 / 4 = 10, to pomeni, da imamo 10 elementov v polju.

 //Izpišimo vse na zaslon
while (count--) {
   printf("Vrednost na lokaciji %x = %d\r\n", ptr, *ptr);  //Izpiši vrednost, ki se nahaja na lokaciji, kamor kaže ptr
   ptr++; //Povečaj ptr. !!! Ne pozabi. Ptr je pointer tipa int, ker je int sam po sebi velik 4-byte, se ptr spremenljivka poveča za 4 in ne za 1. !!!
}

//Izpišimo še na drugačen način
count = sizeof(polje) / sizeof(int);
while (count--) {
   printf("Vrednost na lokaciji %x = %d\r\n", ptr, *ptr++);  //Izpiši vrednost, ki se nahaja na lokaciji, kamor kaže ptr in za tem, ko si prebral, povečaj pointer.
}


Ker smo samo brali, bomo sedaj še pisali z uporabi pointerja in polja.

Koda: Izberi vse
int polje[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *ptr;
int count;

ptr = polje;
count = sizeof(polje) / sizeof(int);

while (count--) {
   //Vse izmed spodnjih opcij so pravilne
   
   *ptr = 2 * *ptr; //NOVO: Povečaj vrednost za 2, zapiši novo vrednost
   // *ptr = *ptr * 2;
   // *ptr = 2 * (*ptr);
   printf("Vrednost na lokaciji %x = %d\r\n", ptr, *ptr++);
}


Debata tukaj
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 02 Okt 2016, 11:55

Deževna nedelja je izjemna za novo poglavje.

8. Pointerji in funkcije

Če hočemo podati pointer kot parameter funkciji, je potrebno funkcijo primerno deklarirati:
Koda: Izberi vse
void funkcija(int* ptr) {
   //koda
}


Zgornja funkcija sprejme naslov neke spremenljivke tipa int. Z tem naslovom sedaj lahko dela operacije branja ali pisanja.
Vsako pisanje z zvezdico (kot je spodaj v primeru) spremeni vrednost kamor kaže pointer! Operacija je vidna tudi "navzven" in ni le lokalne narave.
Ker je funkcija kot takšna sprejela kopijo naslova bo vsaka aritmetična operacija (ptr++, ptr--) delovala le znotraj te funkcije in na ven ne bo imela efekta.

Primer:
Koda: Izberi vse
void funkcija(int* ptr) {
   //

   //Kvadriraj vrednost kamor kaže pointer in shrani novo vrednost
   *ptr = (*ptr) * (*ptr);

   //Povečaj ptr spremenljivko
   //Ta stavek je za podatke izven funkcije nepomemben saj smo kot parameter podali KOPIJO naslova, in ne dejansko referenco.
   ptr++;
}

int main() {
   int stevilka = 10;
   int *lokacija = &stevilka;
   
   //Izpiši prvi vrednost, kliči funkcijo z pointerjem na našo vrednost in izpiši novo vrednost, če je drugačna.
   printf("Osnovna vrednost: %d; Lokacija: %x", stevilka, lokacija);
   funkcija(lokacija);
   printf("Spremenjena vrednost: %d; Lokacija: %x", stevilka, lokacija);

   //Drugi primer uporabe klica naše funkcije, ki sprejme pointer je sledeč:
   funkcija(&stevilka); //Direkt podamo KOPIJO naslova, ni potrebno shraniti v svojo spremenljivko najprej.
}


Zgornji primer kaže, če podamo pointer funkciji, ki vsebuje na naši lokaciji en int element.
1. Kaj pa, če želi naša funkcija sprejeti pointer, ki kaže na polje elementov?
2. Kako potem zagotoviti in povedati, koliko elementov ima naše polje?


Odgovora:
1. Če želimo podati polje elementov funkciji kot parameter, uporabimo klasično definicijo funkcije, ki sprejme en pointer.
2. Ker je C zelo nizek nivo programiranja, pointer ne ve koliko elementov vsebuje naše polje. Spremenljivka, ki je pointer in gleda vse elemente se lahko poljubno sprehaja po pomnilniku, dokler enkrat ne naleti na branje/pisanje na lokacijo, ki ji procesor ne dovoli in program se sesuje. Da bi funkcija vedela, koliko elementov ima naše polje, moram uporabiti dodaten parameter in poslati vrednost funkciji.

Primer:
Koda: Izberi vse

void print_numbers(int* ptr, size_t stevilo_elementov) {
   //Dokler je stevilo elementov vec od 0
   while (stevilo_elementov--) {
      //Printaj stevilo kamor kaže pointer, nato povečaj sam pointer, da kaže na naslednjo lokacijo int polja
      printf("Stevilo: %d\n", *ptr++);
   }
}

int main() {
   int polje[] = { 1, 2, 3, 4, 5, 6 };                  //Ustvari polje tipa int z 6 elementi
   size_t st_elementov = sizeof(polje) / sizeof(int);      //Določi število elementov polja

   //Izpiši vsa števila na zaslon. Parametra sta pointerja na prvi element polja ter velikot samega polja (število elementov v polju)
   print_numbers(polje, st_elementov);
   
   return 0;
}


Zgoraj je primer funkcije, ki izpiše vsak element posebej v polju spremenljivk. Implementacija funkcije in način, kako izpisovati jih je več. Sam se vedno poslužujem najkrajšega za napisat.
Spodaj prilagam primere, kako lahko napišemo funkcije, ki bodo imele isti efekt:

Koda: Izberi vse
void print_numbers(int* ptr, size_t stevilo_elementov) {
   //Dokler je stevilo elementov vec od 0
   while (stevilo_elementov--) {
      //Printaj stevilo kamor kaže pointer, nato povečaj sam pointer, da kaže na naslednjo lokacijo int polja
      printf("Stevilo: %d\n", *ptr++);
   }
}
void print_numbers1(int* ptr, size_t stevilo_elementov) {
   size_t i;
   for (i = 0; i < stevilo_elementov; i++) {
      printf("Stevilo: %d\n", *ptr++);
   }
}
void print_numbers2(int* ptr, size_t stevilo_elementov) {
   size_t i;
   for (i = 0; i < stevilo_elementov; i++) {
      //Od lokacije, ki jo kaže ptr dodaj offset bytov i * sizeof(int), ker je ptr pointer tipa int, int brez pointerja pa je 4-byte velik
      printf("Stevilo: %d\n", ptr[i]);
   }
}
void print_numbers3(int* ptr, size_t stevilo_elementov) {
   size_t i;
   for (i = 0; i < stevilo_elementov; i++) {
      //Zelo zanimiv je ta primer
      printf("Stevilo: %d\n", i[ptr]);
   }
}
void print_numbers4(int* ptr, size_t stevilo_elementov) {
   size_t i;
   for (i = 0; i < stevilo_elementov; i++) {
      //Če je i = 1, potem se ptr poveča za 4, saj je ptr pointer tipa int, ki ima 4-byte!
      printf("Stevilo: %d\n", *(ptr + i));
   }
}

int main() {
   int polje[] = { 1, 2, 3, 4, 5, 6 };                  //Ustvari polje tipa int z 6 elementi
   size_t st_elementov = sizeof(polje) / sizeof(int);      //Določi število elementov polja

   //Izpiši vsa števila na zaslon. Parametra sta pointerja na prvi element polja ter velikot samega polja (število elementov v polju)
   print_numbers(polje, st_elementov);
   printf("------\n");
   print_numbers1(polje, st_elementov);
   printf("------\n");
   print_numbers2(polje, st_elementov);
   printf("------\n");
   print_numbers3(polje, st_elementov);
   printf("------\n");
   print_numbers4(polje, st_elementov);
   printf("------\n");
   
   return 0;
}


Razlaga, kaj je to size_t:
According to the 1999 ISO C standard (C99), size_t is an unsigned integer type of at least 16 bit (see sections 7.17 and 7.18.3).

size_t is an unsigned data type defined by several C/C++ standards, e.g. the C99 ISO/IEC 9899 standard, that is defined in stddef.h.1 It can be further imported by inclusion of stdlib.h as this file internally sub includes stddef.h.

This type is used to represent the size of an object. Library functions that take or return sizes expect them to be of type or have the return type of size_t. Further, the most frequently used compiler-based operator sizeof should evaluate to a constant value that is compatible with size_t.

V slovenščini to pomeni, da je size_t tip, ki je vsaj 16-biten in je namenjen temu, da programer ve, da spremenljivka predstavlja velikost nekega polja ali podobno. Nič drastičnega!

8.1 Pointerji, funkcije in stringi

Kadar vemo, da je naše polje tipa char in da je veljaven string, torej vsebina in na koncu 0 znak, ki označuje konec stringa, potem naši funkciji ni potrebno podati števila elementov v stringu.
Primer je funkcija strlen, ki vrne velikost stringa. Ona le zahteva pointer na string in to je to.

Spodaj bom pokazal primer, kako s funkcijo sprejeti string in izpisati vsak znak posebej:
Koda: Izberi vse
//Funkcija izpiše string vertikalno na zaslon
void izpisi_znake(char* str) {
   //Dokler je vrednost na lokaciji kamor kaže str spremenjivka večje od 0, ker 0 označuje konec stringa
   while (*str) {
      printf("%c\n", *str);
      str++;
   }
   printf("\n"); //Izpiši prazno vrstico
}

int main() {
   char tilz0R[] = "tilz0R"; //String ima 7 bytov rezerviranega prostora, zaradi zaključne 0, ki označuje konec stinga

   //Izpiši mojo spremenljivko
   izpisi_znake(tilz0R);

   //Izpiši znak po znak v stringu
   izpisi_znake("C Pointerji");

   return 0;
}


8.1 Vračanje pointerja iz funkcije

Funkcija lahko tudi vrne pointer, ne samo sprejme.
Deklaracija funkcije, ki vrne pointer dodamo zvezdico, podobno kot to naredimo pri spremenljivki:

Primer
Koda: Izberi vse
int moja_vrednost = 10;

//Funkcija vrne vrednost, kje v ramu se nahaja spremenljivka moja_vrednost, ki je globalna spremenljivka.
int* vrniLokacijo(void) {
    return &moja_vrednost;
}


Kadar funkcija vrača pointer je pomembno, da se zavedamo sledečega. Funkcija nikoli ne sme (mislim, lahko, ampak pričakujete težave v programu) vrniti pointerja na spremenljivko, ki je bila ustvarjena znotraj funkcije (lokalna spremenljivka, razen če je spremenljivka statična (se nahaja v RAM-u in ne na stack-u).
Razlog tiči v tem, da so lokalne spremenljivke shrnajene na stacku, ki pa se vedno spreminja, ko kličemo funkcije in pisanje po znani lokaciji lahko povzroči "blue screen" scenarije.


Primer funkcije, ki dobi pointer na string, odstrani prvi dve črki in vrne nov pointer.
Koda: Izberi vse

char* odrezi2byta(char* stavek) {
   //Podali smo kopijo naslova lokacije stavka naši funkciji.
   //Stavek je sedaj lokalna spremenljivka. Vsaka sprememba navzven nima vpliva, razen ce naredimo "return" in jo shranimo ponovno.
   stavek++;
   stavek++;

   //Vrni novo lokacijo nasega stringa.
   return stavek;
}

//Uporaba
char str[] = "To je moj string";
char* ptr = str;

//Izpisi originalni string
printf("String1: %s\r\n", ptr);

//Obdelaj string in vrni novo lokacijo
ptr = odrezi2byta(ptr);

//Izpisi nov string
printf("String2: %s\r\n", ptr);



Domača naloga: Napišite funkcijo, ki izračuna "c" stranico (tisto, najdaljšo ;) ) pravokotnega trikotnika. Funkcija naj izgleda sledeče:
  • Funkcija sprejme 3 parametre. Prva dva sta stranici a in b in jih podajte brez pointerja, tretja, rezultat, je pa spremenljivka, kamor boste shranili vrednost.
  • Funkcija naj vrne 1, če je bil izračun uspešen, drugače 0. Da bo izraun uspešen, morata biti a in b vrednosti različni (!) od 0.
  • Hint: math.h je C knjižnica, kjer najdete funkcijo za korenjenje, če ga potrebujete.

Na zaslon izpišite vrednost LE, če je funkcija uspešno naredila svoje delo.

Vprašanja glede DN in ostalega najdete v temi za debato.

Naslednja tema: Strukture, pointerji in funkcije.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 02 Okt 2016, 15:01

9. Strukture, pointerji in funkcije

Struktura je skupina nekih spremenljivk, ki imajo med seboj logičen pomen in se povezujejo.
Primer strukture je struktura za seštevanje dveh števil, ki hrani tudi rezultat.

Da ponovim o strukturah na hitro, deklariramo jih z posebnim ukazom struct. Poznamo več načinov deklaracije in uporabe struktur, vendar to ni tema tega poglavja.
Tukaj bom uporabljal metodo, ki jo največkrat uporabljam in sicer:

Koda: Izberi vse
typedef struct {
   float a;           //Število a
   float b;           //Število b
   double rezultat; //Rezultat različnih operacij
} matematika_t; //Ime je poljubno, jaz sem dal matematika_t. _t predstavlja typedef kar oznanja da je to nov tip. To je le programerju prijazen prikaz in ni potreben.


Naredili smo svojo strukturo, ki vsebuje 2 vhodni števili in rezultat operacije med njima. Vsa tri števila imajo med seboj smisel in se navezujejo, zato jih je pametno zapakirati v svojo strukturo.

Njeno velikost v pomnilniku pove spodnji ukaz:
Koda: Izberi vse
sizeof(matematika_t);

To potrebujemo vedeti, saj ob uporabi pointer-ja na strukturo z ukazom ptr++ povečamo vrednost ptr spremenljivke za velikost strukture!


V našem primeru imamo 2 float spremenljivki in eno double, kar je skupaj 16-bytov pomnilnika.

Struktura_Memory.png
Struktura_Memory.png (7.95 KiB) Videno 1088 krat


9.1 Deklaracije spremenljivke naše strukture

Struktura je sedaj postala podatkovni tip, podobno kot je to int, char in ostali.

Deklaracije spremenljivke naše strukture je sledeča:
Koda: Izberi vse
matematika_t m; //Ustvari spremenljivko m tipa matematika_t


Za dostop do spremenljivk a, b in rezultat se uporablja znak pika ".".
Koda: Izberi vse
m.a = 5;    //Nastavi vrednost a = 5
m.b = 10;  //Nastavi vrednost b = 10
m.rezultat = m.a + m.b; //seštej vrednosti in shrani v rezultat iste strukture.


9.2 Deklaracija pointerja na strukturo

Ker je struktura nov tip podatka, lahko tudi deklariramo pointer, ki bo tipa te strukture.
Koda: Izberi vse
//Deklaracija pointerja tipa matematika_t, ki kaže na prej ustanovljeno spremenljivko m.
matematika_t* ptr = &m;


Ker smo sedaj v kazalčnem dostopu (pointer access), dostop do spremenljivk znotraj strukture ni možen z piko, pač pa se uporablja nov simbol in sicer ->, ki predstavlja dostop preko pointerja.

Primer:
Koda: Izberi vse
ptr->a = 5;    //Nastavi vrednost a = 5
ptr->b = 10;  //Nastavi vrednost b = 10
ptr->rezultat = ptr->a + ptr->b; //seštej vrednosti in shrani v rezultat iste strukture.


Sedaj vemo, kako se dostopa do spremenljivk znotraj strukture preko pointerja. V nadaljevanju bom razložil, kaj se dejansko dogaja, oz. kako se ta dostop dogaja na najnižjem nivoju.
Razlaga bo potekala ob sliki, kjer je struktura predstavljena v pomnilniku.

Deklarirano imamo spremenljivko tipa matematika_t, ki je na lokaciji 0x1000, za lažje računanje.

Koda: Izberi vse
matematika_t m; //Predvidevajmo, da je spremenljivko naš linker postavil na lokacijo 0x1000.
matematika_t* ptr = &m; //ptr vsebuje lokacijo spremenljivke m, ki je v našem primeru 0x1000,


&m nam vrne lokacijo prvega elementa v strukturi! V našem primeru je to lokacija spremenljivke a


Imamo strukturo v pomnilniku in tudi pointer, ki kaže na njo. Če hočemo pisati v spremenljivko rezultat znotraj strukture preko pointerja, to naredimo tako:
Koda: Izberi vse
//Zapiši 10 v rezultat.
ptr->rezultat = 10;


Prevajalnik ve, da je rezultat tretji element naše strukture. Ve tudi, katerega tipa so elementi pred njim.
Če pogledamo prvo sliko, vidimo, da so v pomnilniku (NAČELOMA!) elementi eden za drugim in zasedejo prostora, kot podatkovni tip potrebuje.

V našem primeru sta pred spremenljivko rezultat 2 float elementa, kar pomeni 8 bytov je spredaj pred rezultatom.
Tretji stolpec na sliki prikazuje kakšen zamik glede na strukturo nam predstavlja spremenljivka. Vidimo, da ima spremenljivka rezultat 8-bytov zamika.

Določeno je tudi, da je spremenljivka tipa double, kar pomeni 8-bytov pomnilnika.

Zgornji zapis pisanja na lokacijo je ekvivalenten spodnjemu:
Koda: Izberi vse
//TIP 1:
//Zapis preko pointerja
ptr->rezultat = 10;

//EKVIVALENT, ki se zgodi na nivoju assemblerja:
//ptr se nahaja na lokaciji 0x1000
//Ustvari spremenljivko, ki je pointer tipa double, saj je rezultat tipa double.
double* rezultat_ptr = 0x1000 + 0x0008; //Rezultat je v strukturi zamaknjen za 8-bytov

//Zapiši vrednost 10 na lokacijo rezultat v strukturi:
*rezultat_ptr = 10;


Če je res potreba po tem, da vemo koliko je offset nekega elementa v strukturi, se lahko uporabi offsetof makro v C-ju. Nahaja se v stddef.h knjižnici.

Primer, kako lahko na več načinov pišemo v spremenljivko rezultat znotraj strukture m:
Koda: Izberi vse
//Struktura za matematiko
typedef struct {
   float a;
   float b;
   double rezultat;
} matematika_t;

int main() {
   matematika_t m;
   matematika_t* ptr = &m;
   double* rezultat_ptr;

   //Zapiši brez pointerja
   m.rezultat = 10;
   printf("m.rezultat = %f\n", m.rezultat);

   //Zapiši s pointerjem
   ptr->rezultat = 15;
   printf("m.rezultat = %f\n", m.rezultat);

   //Dejansko lokacijo rezultat spremenljivke znotraj m strukture lahko dobimo na 2 načina:
   rezultat_ptr = &m.rezultat;
   rezultat_ptr = &ptr->rezultat;

   //Pisanje direkt na lokacijo rezultata:
   *rezultat_ptr = 20;
   printf("m.rezultat = %f\n", m.rezultat);
}


9.3 Funkcije in pointerji

Pointer na strukturo se večinoma uporablja le znotraj funkcij, kar pa še zdaleč ni nujno da se drugod ne. Tudi če se drugje, to ni napačen pristop v C-ju!

Primer funkcije, ki sprejme pointer na našo strukturo in manipulira z podatki:
Koda: Izberi vse
//Seštej 2 vrednosti
void sestevaj(matematika_t* ptr) {
   //Seštej vrednosti. Ker je podan pointer uporabljamo '->' namesto '.' znaka.
   ptr->rezultat = ptr->a + ptr->b;
}


Funkcijo pokličemo tako:
Koda: Izberi vse
m.a = 10;
m.b = 20;

//Seštej vrednosti a in b in jih shrani v rezultat.
sestevaj(&m);

//Tukaj nismo v pointer dostopu, zato uporabljamo piko za dostop do memberja strukture.
printf("Rezultat 10 + 20 = %d", m.rezultat);


9.4 Struktura znotraj strukture

Strukture so tip podatka, ki omogočajo dodajanje drugih podatkovnih tipov vase. In podatkovni tip je lahko tudi druga struktura.
Sam uporabljam takšen primer ponekod:

Koda: Izberi vse
//Struktura za datum
typedef struct {
   uint8_t dan;
   uint8_t mesec;
   uint16_t leto;
} datum_t;

//Struktura za čas
typedef struct {
   uint8_t ure;
   uint8_t minute;
   uint8_t sekunde;
} cas_t;

//Oba skupaj tvorita datum in čas
typedef struct {
   datum_t datum;
   cas_t cas;
} datumcas_t;

int main() {
   datumcas_t dt; //Struktura tipa datumcas_t, ki vsebuje dve strukturi
   datumcas_t* ptr = &dt; //ptr vsebuje lokacijo spremenljivke dt

   //Dostop do vrednosti ure je sledeč:
   //Dostop je narejen brez pointerja
   dt.cas.ure = 10;
   dt.cas.minute = 59;
   dt.cas.sekunde = 15;

   //Dostop do ure spremenljivke znotraj cas spremenljivke znotraj dt spremenljivke:
   ptr->cas.ure = 11;

   return 0;
}


Zakaj enkrat -> (puščica), drugič pa. (pika), čeprav smo dostopali preko pointerja?

Ko smo z pointerjem ptr dostopali do cas dela strukture, smo za to potrebovali dostop preko pointerja (pointer access). V tem trenutku je pointer dostop izničen in naprej se uporablja znak pike za dostop do globjih elementov, dokler spet ne naletimo na pointer dostop.

Domača naloga: Tokrat ste prosti. Bo vmes objavljena oz. v naslednjem poglavju.

Tema za debato.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 08 Okt 2016, 10:38

10. Void pointer in casting med pointer tipi

V tem poglavju se bomo ukvarjali s posebnim void* tipom ter kako lahko pointer nekega tipa castamo na drugi tip in nad njim izvajamo operacije.

10.1 Void pointer

Void pointer podatkovni tip je poseben tip, ki sam po sebi ne pove nič o podatku na lokaciji kamor kaže, pač pa samo pove, "jaz sem pointer in nekam kažem".

Spremenljivko tipa void* deklariramo kot:
Koda: Izberi vse
void* ptr;   //Deklaracija tipa void *, ki nekam kaže, ne ve pa na kakšne podatke.


Ker void tip sam po sebi ne pove nič o podatkih, smo tudi omejeni pri operacijah z takšnim pointerjem
Primer:
Koda: Izberi vse
void* ptr;  //Deklaracija pointerja tipa void

ptr++;   //Napaka! Spremenljivke tipa void pointer ni mogoče povečati ali zmanjšati na takšen način, saj ne vemo za koliko bytov povečati, ker tip void nima podatkovne velikosti.


Zakaj je void pointer sploh uporaben in zakaj se ga uporablja?
  • Uporaben je zato, ker v njega lahko shranimo drugi tip pointerja brez težav. V tem primeru moramo vedeti kakšen tip pointerja smo shranili v void pointer, saj se samo naslov prekopira(!).
  • Uporablja se povsod, kjer pričakujemo le shranitev nekega naslova, ne zanima pa nas v tistem momentu, kakšen bo ta tip.

Primer uporabe:
Koda: Izberi vse
void* ptr;   //Deklaracija tip void*, shrani samo naslov
int a = 10;   //Deklaracija tipa int, ki ima vrednost 10.

ptr = &a;   //Shrani samo lokacijo spremenljivke a, spremenljivka ptr ne ve, da smo shranili pointer tipa int


Zgoraj smo naredili pointer tipa void in spremenljivko tipa int, ki ima vrednost 10. V pointer smo shranili lokacijo spremenljivke a, vendar ptr spremenljivka ne ve nič o tipu spremenljivke a. Ve le naslov spremenljivke.

O tem, kako pisati, brati in izvajati aritmetične opreracije s takšnim pointerjem bo pa govora v naslednjem poglavju, kjer bomo obravnavali casting (pretvarjanje) med tipi podatkov in pointerji.

10.2 Casting

Casting (slovenski izraz ne vem kaj bi lahko bil pameten) pomeni pretvarjanje med podatkovnimi tipi.
Za primer bom pokazal, kako castati spremenljivko float v spremenljivko int.

Kadar hočemo izvesti casting, moramo pred spremeljivko, ki jo želimo castati dodati podatkovni tip v oklepajih.
Več spodaj v prvem primeru.

Float vsebuje tudi decimalne številke, medtem ko int predstavlja le cela števila.
Pri castanju iz float v int se ohrani samo celo število, medtem ko se decimalke izbrišejo (brez zaokroževanja!)


Primer:
Koda: Izberi vse
float a = 3.345;   //Ustvari spremenljivko a
int b = (int)a;      //Ustvari spremenljivko b in castaj float a v integer.

printf("A: %f", a); //Za izpis float števila se uporablja %f
printf("B: %d", b); //Za izpis celega števila se uporablja %d


Primer izpiše:
A: 3.345
B: 3

Tako smo float castali v int.

Casting pointerjev

Zgoraj smo omenili void pointer, ki sam po sebi nima znanega tipa podatka na lokaciji, kamor kaže.
Če hočemo izvajati operacija z void pointerjem, moramo ta pointer najprej castati v tip podatka, kot ga želimo prebrati.

Primer:
Koda: Izberi vse
int a = 10;      //Ustvari spremenljivko a
void* ptr = &a; //Shrani lokacijo spremenljivke a, casting ni potreben ker v void* lahko shranimo karkoli

int* a_ptr;      //Deklaracija spremenljivke a_ptr, ki bo kazala pointer na a preko castanega void pointerja

//a_ptr = ptr;   //ERROR: Potreben je casting ptr spremenljivke, preden jo shranimo
a_ptr = (int *)ptr;   //SUCCESS: Spremenljivko ptr smo castali v (int *), ker je a_ptr tipa (int *)

//Od tu naprej ima a_ptr shranjen naslov spremenljivke a, vse operacije se izvajajo kot int.

//Preberi vrednost spremenljivke a z pointerjem
printf("A preko pointerja: %d\n", *a_ptr);


V zgornjem primeru smo pred branjem shranili void pointer v drugo spremenljivko.
Da se izognemu večim spremenljivkam, lahko beremo/pišemo/aritmiramo sproti med izvajanjem castinga.

Primer:
Koda: Izberi vse
int a = 10;      //Ustvari spremenljivko a
void* ptr = &a; //Shrani lokacijo spremenljivke a, casting ni potreben ker v void* lahko shranimo karkoli

//Preberi vrednost spremenljivke a z void pointerjem, ki je pred branjem castan na (int *)
//Zvezdica izven oklepajev casting pomeni branje kar je na lokaciji kamor kaže ptr, (int *) pa pomein, da bi radi brali podatke tipa int
printf("A preko pointerja: %d\n", *(int *)ptr);


Primer pisanja in povečevanja pointerja, ki je void:
Koda: Izberi vse
int arr[2] = {1, 2};   //ustvari array dveh vrednosti, ki jih bomo spremenili z pointerjem s pomočjo aritmetike.
void* ptr = arr;      //Shrani lokacijo arr polja, casting ni potreben ker v void* lahko shranimo karkoli

//Izpiši originalni vrednosti
printf("arr[0]: %d, arr[1]: %d\n", arr[0], arr[1]);

//Povečaj vrednost prvega elementa v arrayu s pomočjo void pointerja
*(int *)ptr = *(int *)ptr + 10;
//Povečaj pointer za en integer
ptr = ((int *)ptr) + 1; //Povečava NI bila za en byte, pač pa za 4, saj je integer velik 4 byte!
//Povečaj vrednost drugega elementa v arrayu s pomočjo void pointerja
*(int *)ptr = *(int *)ptr + 10;

//Izpiši nove vrednosti
printf("arr[0]: %d, arr[1]: %d\n", arr[0], arr[1]);


Casting pa ni uporaben samo, če imamo tip void in hočemo manipulirati s podatki.
Casting iz unsigned char (v nadaljevanju uint8_t) v unsigned integer (v nadaljevanju uint32_t) je pri meni osebno kar pogost.

Zakaj?
Primer so podatki za zapis v flash v mikrokontrolerju.
UART jih pošilja byte po byte in shranjuje v array tipa uint8_t, medtem ko jih mikrokontroler zna shraniti v flash kot uint32_t (torej 4 byte hkrati).
Tedaj lahko naredimo cast pri čemer se moramo zavedati, da se število elementov zmanjša. Več v spodnjem primeru:

Koda "shrani" v flash podatke, ki ji posreduješ. Ker lahko shranjuje po 4 byte hkrati, bo z castanjem to tudi naredila:
Koda: Izberi vse
//Shrani podatke v flash, sprejmi pointer na podatke za shranitev ter število bytov za shranitov
void shraniFlash(uint8_t* data, size_t count) {
   size_t blocks;      //Število blokov naših podatkov

   //Ker naša naprava lahko shranjuje 4 podatke hkrati, lahko "pretvorimo" podatke
   uint32_t* ptr = (uint32_t *)data;

   //Izračunaj koliko blokov po uint32_t imamo
   blocks = count / sizeof(uint32_t);

   printf("Shranjujem v flash\n");

   //Pojdi skozi vse bloke podatkov, velikosti uint32_t
   while (blocks--) {
      //Preberi podatek na lokaciji z pointerjem ter povečaj pointer za 4
      printf("Podatek za shranitev: %08X\n", *ptr++);
   }

   //Če slučajno dolžina vhodnih podatkov ni deljiva z velikostjo bloka, ostale procesiraj kot byte
   blocks = count % sizeof(uint32_t);
   printf("Ostanek podatkov: %d\n", blocks);
   data = (uint8_t *)ptr;

   while (blocks--) {
      printf("Podatek za shranitev: %02X\n", *data++);
   }
   printf("Shranjeno\n\n");
}

//Struktura, ki bo šla v flash
typedef struct {
   int a;
   int b;
   char ime[10];
} moja_struktura;

int main() {
   uint8_t data[103]; //Imamo 100 bytov podatkov
   uint8_t i;

   //Struktura, ki gre v flash
   moja_struktura mojaStruktura;
   mojaStruktura.a = 10;
   mojaStruktura.b = 4;

   //Zapolni podatke, da ne bodo vsi na 0
   for (i = 0; i < sizeof(data) / sizeof(uint8_t); i++) {
      data[i] = i;
   }

   //Pokliči funkcijo, ki shranjuje podatke
   //Podaj ji pointer na podatke ter število bytov našega spomina
   shraniFlash(data, sizeof(data));

   //Sedaj pa shranimo v flash strukturo podatkov. Kako bodo podatki zgledali v flashu nas ne zanima
   //Z castanjem v uint8_t* smo povedali funkciji naše podatke ter velikost le-teh v pomnilniku.
   //Ostalo prepustimo funkciji, ki prejme podatke kot pointer na uint8_t
   shraniFlash((uint8_t *)&mojaStruktura, sizeof(mojaStruktura));

   return 0;
}


Spodaj prilagam primer, kako moja knjižnica za STM32 vpisuje podatke v flash pomnilnik.
TM_FLASHLOADER_ProcessPacket funkcija:

Koda: Izberi vse
/**   
 * |----------------------------------------------------------------------
 * | Copyright (C) Tilen Majerle, 2015
 * |
 * | This program is free software: you can redistribute it and/or modify
 * | it under the terms of the GNU General Public License as published by
 * | the Free Software Foundation, either version 3 of the License, or
 * | any later version.
 * | 
 * | This program is distributed in the hope that it will be useful,
 * | but WITHOUT ANY WARRANTY; without even the implied warranty of
 * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * | GNU General Public License for more details.
 * |
 * | You should have received a copy of the GNU General Public License
 * | along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * |----------------------------------------------------------------------
 */
#include "tm_stm32f4_flashloader.h"

/* All FLASH flags */
#define FLAG_ALL (FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR |  \
                  FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR)

/* Private typedef */
typedef struct {
   uint32_t ApplicationAddress;     /*!< Application start address */
   uint16_t ApplicationStartSector; /*!< Application start sector */
   uint32_t ApplicationProgramSize; /*!< Application program size */
   uint16_t ApplicationEndSector;   /*!< Application end sector */
   uint32_t FlashEndAddress;        /*!< Flash end address in device */
   uint16_t FlashEndSector;         /*!< Flash end sector in device */
   uint32_t WritePointer;           /*!< Current write pointer for next data packet save */
   uint8_t SectorsErased;           /*!< Set to 1 when sectors for application are already erased */
   uint8_t State;                   /*!< Current bootloader programming state */
   uint8_t AffectedSectorsCount;    /*!<Number of affected sectors used for program/settings */
} FLASHLOADER_t;

/* Private variable */
FLASHLOADER_t FLASHLOADER;

uint8_t TM_FLASHLOADER_Begin(uint32_t ApplicationAddress, uint32_t ApplicationSize) {
   uint16_t sector;
   
   /* Save application address location */
   FLASHLOADER.ApplicationAddress = FLASHLOADER_ADDRESS(ApplicationAddress);
   
   /* Get flash last location in device */
   FLASHLOADER.FlashEndAddress = 0x08000000 + (uint32_t) (*(__IO uint16_t *) (0x1FFF7A22)) * 1024 - 1;
   
   /* Store application program size */
   if (ApplicationSize > 0) {
      FLASHLOADER.ApplicationProgramSize = ApplicationSize;
   } else {
      /* Set application program size */
      FLASHLOADER.ApplicationProgramSize = FLASHLOADER.FlashEndAddress - FLASHLOADER.ApplicationAddress;
   }
   
   /* Check application address */
   if (FLASHLOADER.FlashEndAddress <= FLASHLOADER.ApplicationAddress) {
      /* No go, not in side flash area */
      return 1;
   }
   
   /* Check if available memory in flash */
   if ((FLASHLOADER.FlashEndAddress - FLASHLOADER.ApplicationAddress) < FLASHLOADER.ApplicationProgramSize) {
      /* No go, no memory available for whole data */
      return 1;
   }
   
   /* Get application sectors */
   FLASHLOADER.ApplicationStartSector = TM_FLASHLOADER_GetSector(FLASHLOADER.ApplicationAddress);
   FLASHLOADER.FlashEndSector = TM_FLASHLOADER_GetSector(FLASHLOADER.FlashEndAddress - 1);
   
   /* Calculate application end sector */
   FLASHLOADER.ApplicationEndSector = TM_FLASHLOADER_GetSector(FLASHLOADER.ApplicationAddress + FLASHLOADER.ApplicationProgramSize - 1);
   
   /* Ready */
   FLASHLOADER.State = 1;
   
   /* Reset everything */
   FLASHLOADER.WritePointer = FLASHLOADER.ApplicationAddress;
   FLASHLOADER.SectorsErased = 0;
   FLASHLOADER.AffectedSectorsCount = 0;
   
   /* Get all sectors */
   for (sector = FLASHLOADER.ApplicationStartSector; sector < FLASHLOADER.ApplicationEndSector + 1; sector += 8) {
      /* Increase number of sectors */
      FLASHLOADER.AffectedSectorsCount++;
   }
   
   /* Clear flash flags */
   FLASH_ClearFlag(FLAG_ALL);
   
   /* Unlock flash */
   FLASH_Unlock();
   
   /* Enable access to read/write flash */
   FLASH_OB_Unlock();   
   
   /* Clear flash flags */
   FLASH_ClearFlag(FLAG_ALL);
   
   /* Disable write protection for sectors where application program will be stored */
   TM_FLASHLOADER_DisableWP();
   
   /* Everything OK */
   return 0;
}

uint8_t TM_FLASHLOADER_End(void) {
   /* Write protect sectors */
   //TM_FLASHLOADER_EnableWP();
   
   /* Wait flash for last operation */
   FLASH_WaitForLastOperation();
   
   /* Lock flash */
   FLASH_Lock();
   
   /* Everything OK */
   return 0;
}

uint32_t TM_FLASHLOADER_ProcessPacket(uint8_t* data, uint32_t count) {
   uint32_t status;
   uint32_t blocks;
   
   /* Check if sectors erased */
   if (TM_FLASHLOADER_EraseSectors()) {
      /* Return error */
      return 1;
   }
   
   /* Clear all flash flags */
   FLASH_ClearFlag(FLAG_ALL);
   
   /* Status flag */
   status = 0;
   
   /* Get number of blocks in buffer */
   blocks = count >> 2;
   
   /* Write data to flash memory */
   while (blocks--) {
      /* Check if application end address has reached */
      if (FLASHLOADER.WritePointer >= FLASHLOADER.ApplicationAddress + FLASHLOADER.ApplicationProgramSize) {
         /* No memory available for program anymore */
         return 2;
      }
      
      /* Program flash as words */
      status += FLASH_ProgramWord(FLASHLOADER.WritePointer, *(uint32_t *)data) != FLASH_COMPLETE;
      
      /* Increase pointer */
      FLASHLOADER.WritePointer += 4;
      data += 4;
   }
   
   /* Write remaining data */
   count = count % 4;
   
   /* Write to flash */
   while (count--) {
      /* Check if application end address has reached */
      if (FLASHLOADER.WritePointer >= FLASHLOADER.ApplicationAddress + FLASHLOADER.ApplicationProgramSize) {
         /* No memory available for program anymore */
         return 2;
      }
      
      /* Program flash as bytes */
      status += FLASH_ProgramByte(FLASHLOADER.WritePointer++, *(__IO uint8_t *)data++) != FLASH_COMPLETE;
   }
   
   /* Everything OK = status = 0 */
   return status;
}

uint8_t TM_FLASHLOADER_EraseSectors(void) {
   uint16_t sector;
   uint32_t irq;
   
   /* Erase sectors if the are not already */
   if (FLASHLOADER.SectorsErased) {
      /* Erase all affected sectors */
      return 0;
   }
   
   /* Disable all interrupts, get current interrupt status */
   irq = __disable_irq();
   
   /* Do a sectors reset */
   for (sector = FLASHLOADER.ApplicationStartSector; sector < FLASHLOADER.ApplicationEndSector + 1; sector += 8) {
      /* Clear flash flags */
      FLASH_ClearFlag(FLAG_ALL);
      
      /* Do a sector erase */
      if (FLASH_EraseSector((uint32_t)sector, FLASHLOADER_VOLTAGE_RANGE) != FLASH_COMPLETE) {
         /* Return error */
         return 1;
      }
   }
   
   /* Set flag, sectors erased */
   FLASHLOADER.SectorsErased = 1;
   
   /* Enable all interrupts */
   if (!irq) {
      __enable_irq();
   }
   
   /* Return OK */
   return 0;
}

uint8_t TM_FLASHLOADER_GetAffectedSectorsCount(void) {
   /* Return affected sectors count */
   return FLASHLOADER.AffectedSectorsCount;
}

uint8_t TM_FLASHLOADER_JumpToProgram(uint32_t FlashAddress) {
   /* New type for function call */
   typedef void (*Bootloader_Function)(void);
   Bootloader_Function Jump_To_Application;
   
   /* Check flash address */
   if (FlashAddress == 0) {
      FlashAddress = FLASHLOADER.ApplicationAddress;
   }
   
   /* Format address */
   FlashAddress = FLASHLOADER_ADDRESS(FlashAddress);
   
   /* Test if user code is programmed starting from address "program_address" */
    if (
      ((*(__IO uint32_t *) FlashAddress) & 0x2FFE0000) == 0x20000000 && /*!< Program is loaded */
      TM_FLASHLOADER_BeforeJump(FlashAddress)                           /*<! Jump is allowed to location */
   ) {
      /* Disable interrupts */
      __disable_irq();
      
      /* Set FLASH vector table */
      SCB->VTOR = 0x08000000 | (FlashAddress & (uint32_t)0x1FFFFF80);
      
      /* Jump to user application, set jump address */
      Jump_To_Application = (Bootloader_Function) *(__IO uint32_t *) (FlashAddress + 4);
      
      /* Initialize user application's Stack Pointer */
      __set_MSP(*(__IO uint32_t *) FlashAddress);
      
      /* Jump to application */
      Jump_To_Application();
      
      /* Return 1 = This should never happen */
      return 1;
    }
   
   /* Return error in case we didn't jump */
   return 0;
}

TM_FLASHLOADER_Sector_t TM_FLASHLOADER_GetSector(uint32_t Address) {
   TM_FLASHLOADER_Sector_t sector = TM_FLASHLOADER_Sector_0;
   
   /* Format address */
   Address = FLASHLOADER_ADDRESS(Address);

   /* Get proper sector */
   if ((Address < ADDR_FLASH_SECTOR_1)) {
      sector = TM_FLASHLOADER_Sector_0;
   } else if ((Address < ADDR_FLASH_SECTOR_2)) {
      sector = TM_FLASHLOADER_Sector_1; 
   } else if ((Address < ADDR_FLASH_SECTOR_3)) {
      sector = TM_FLASHLOADER_Sector_2; 
   } else if ((Address < ADDR_FLASH_SECTOR_4)) {
      sector = TM_FLASHLOADER_Sector_3; 
   } else if ((Address < ADDR_FLASH_SECTOR_5)) {
      sector = TM_FLASHLOADER_Sector_4; 
   } else if ((Address < ADDR_FLASH_SECTOR_6)) {
      sector = TM_FLASHLOADER_Sector_5; 
   } else if ((Address < ADDR_FLASH_SECTOR_7)) {
      sector = TM_FLASHLOADER_Sector_6; 
   } else if ((Address < ADDR_FLASH_SECTOR_8)) {
      sector = TM_FLASHLOADER_Sector_7; 
   } else if ((Address < ADDR_FLASH_SECTOR_9)) {
      sector = TM_FLASHLOADER_Sector_8; 
   } else if ((Address < ADDR_FLASH_SECTOR_10)) {
      sector = TM_FLASHLOADER_Sector_9; 
   } else if ((Address < ADDR_FLASH_SECTOR_11)) {
      sector = TM_FLASHLOADER_Sector_10; 
   } else if ((Address < ADDR_FLASH_SECTOR_12)) {
      sector = TM_FLASHLOADER_Sector_11;
   } else if ((Address < ADDR_FLASH_SECTOR_13)) {
      sector = TM_FLASHLOADER_Sector_12;
   } else if ((Address < ADDR_FLASH_SECTOR_14)) {
      sector = TM_FLASHLOADER_Sector_13;
   } else if ((Address < ADDR_FLASH_SECTOR_15)) {
      sector = TM_FLASHLOADER_Sector_14;
   } else if ((Address < ADDR_FLASH_SECTOR_16)) {
      sector = TM_FLASHLOADER_Sector_15;
   } else if ((Address < ADDR_FLASH_SECTOR_17)) {
      sector = TM_FLASHLOADER_Sector_16;
   } else if ((Address < ADDR_FLASH_SECTOR_18)) {
      sector = TM_FLASHLOADER_Sector_17;
   } else if ((Address < ADDR_FLASH_SECTOR_19)) {
      sector = TM_FLASHLOADER_Sector_18;
   } else if ((Address < ADDR_FLASH_SECTOR_20)) {
      sector = TM_FLASHLOADER_Sector_19;
   } else if ((Address < ADDR_FLASH_SECTOR_21)) {
      sector = TM_FLASHLOADER_Sector_20;
   } else if ((Address < ADDR_FLASH_SECTOR_22)) {
      sector = TM_FLASHLOADER_Sector_21;
   } else if ((Address < ADDR_FLASH_SECTOR_23)) {
      sector = TM_FLASHLOADER_Sector_22;
   } else if ((Address < USER_FLASH_END_ADDRESS)) {
      sector = TM_FLASHLOADER_Sector_23;
   }
   
   return sector;
}

uint32_t TM_FLASHLOADER_GetSectorSize(TM_FLASHLOADER_Sector_t sector) {
   uint32_t size;
   
   /* Check sectors */
   switch (sector) {
      case TM_FLASHLOADER_Sector_0:
      case TM_FLASHLOADER_Sector_1:
      case TM_FLASHLOADER_Sector_2:
      case TM_FLASHLOADER_Sector_3:
      case TM_FLASHLOADER_Sector_12:
      case TM_FLASHLOADER_Sector_13:
      case TM_FLASHLOADER_Sector_14:
      case TM_FLASHLOADER_Sector_15:
         /* 16-kB sector size */
         size = 0x4000;
         break;
      case TM_FLASHLOADER_Sector_4:
      case TM_FLASHLOADER_Sector_16:
         /* 64-kB sector size */
         size = 0x10000;
         break;
      case TM_FLASHLOADER_Sector_5:
      case TM_FLASHLOADER_Sector_6:
      case TM_FLASHLOADER_Sector_7:
      case TM_FLASHLOADER_Sector_8:
      case TM_FLASHLOADER_Sector_9:
      case TM_FLASHLOADER_Sector_10:
      case TM_FLASHLOADER_Sector_11:
      case TM_FLASHLOADER_Sector_17:
      case TM_FLASHLOADER_Sector_18:
      case TM_FLASHLOADER_Sector_19:
      case TM_FLASHLOADER_Sector_20:
      case TM_FLASHLOADER_Sector_21:
      case TM_FLASHLOADER_Sector_22:
      case TM_FLASHLOADER_Sector_23:
         /* 128-kB sector size */
         size = 0x20000;
         break;
      default:
         size = 0;
   }
   
   /* Return size value */
   return size;
}

uint8_t TM_FLASHLOADER_DisableWP(void) {
   uint8_t UserWrpSectors;

   /* Mark all sectors inside the user flash area as non protected */ 
   UserWrpSectors = 0xFFF - ((1 << (FLASHLOADER.ApplicationStartSector / 8)) - 1);

   /* Unlock the Option Bytes */
   FLASH_OB_Unlock();

   /* Disable the write protection for all sectors inside the user flash area */
   FLASH_OB_WRPConfig(UserWrpSectors, DISABLE);

   /* Returns status, 0 = OK, 1 = Error */ 
   if (FLASH_OB_Launch() != FLASH_COMPLETE) {
      /* return error */
      return 1;
   }
   
   /* Lock option bytes */
   FLASH_OB_Lock();
   
   /* Return OK */
   return 0;
}

uint8_t TM_FLASHLOADER_EnableWP(void) {
   uint8_t UserWrpSectors;

   /* Mark all sectors inside the user flash area as non protected */ 
   UserWrpSectors = 0xFFF - ((1 << (FLASHLOADER.ApplicationStartSector / 8)) - 1);

   /* Unlock the Option Bytes */
   FLASH_OB_Unlock();

   /* Disable the write protection for all sectors inside the user flash area */
   FLASH_OB_WRPConfig(UserWrpSectors, ENABLE);

   /* Returns status, 0 = OK, 1 = Error */ 
   if (FLASH_OB_Launch() != FLASH_COMPLETE) {
      /* Return error */
      return 1;
   }
   
   /* Lock option bytes */
   FLASH_OB_Lock();
   
   /* Return OK */
   return 0;
}

uint8_t TM_FLASHLOADER_Read(uint32_t FlashAddress, uint8_t* buff, uint32_t count) {
   /* Format address */
   FlashAddress = FLASHLOADER_ADDRESS(FlashAddress);
   
   /* Do a memory copy */
   memcpy(buff, (uint8_t *)FlashAddress, count);
   
   /* Everything OK */
   return 0;
}

__weak uint8_t TM_FLASHLOADER_BeforeJump(uint32_t FlashAddress) {
   /* Allow jump, return 1 */
   return 1;
}


Domača naloga:
Napišite funkcijo, ki sprejme 2 argumenta. Prvi naj bo pointer na lokacijo podatka, drugi pa naj bo int (1 ali 0), in naj pomeni:
-0: če je parameter 0, potem je prvi parameter tipa int,
-1: če je parameter 1, potem je prvi parameter tipa float.

Glede na prejet tip parametra izpišite obratno vrednost tipa števila na zaslon.
Torej, naredili ste spremenljivko tipa float in pointer podali funkciji. Izpišite znotraj funkcije vrednost prebrano kot int iz iste lokacije. Poizkusite tudi v drugo smer.

Preverite vrednosti in poizkušajte pri sebi razložiti zakaj je tako. Pomoč: preverite zapise podatkovnih tipov v pomnilniku.

Tema za pogovor je na voljo tukaj.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 16 Okt 2016, 15:30

11. Pointer na pointer
Pred nami je zadnje poglavje o pointerjih, in sicer dvojni pointer oz. "pointer na pointer".

V tem razdelju bom uporabjal 2 kratici:
PTR: Oznaka za navadni pointer
PTR2PTR: Oznaka za pointer na pointer.

Pointer (PTR) vs pointer^pointer (PTR2PTR, pointer na pointer)
  • PTR je nekaj, kar kaže na vsebino v pomnilniku, medtem, ko je PTR2PTR nekaj, kar kaže na PTR.
  • S pomočjo PTR lahko spreminjano vsebino, kamor kaže in premikamo samega sebe (lokacijo, kamor bomo kazali)
  • S pomočjo PTR2PTR lahko iz nekje drugje (recimo iz funkcije) spreminjamo, kamor kaže osnovni PTR in tudi lahko spreminjamo podatke, na katere kaže PTR

Deklaracija PTR2PTR je sledeča:
Koda: Izberi vse
tip_pointerja ** ime; //Uporabljani sta 2 zvezdici.


Vsa pravila so enaka kot pri enojmen pointerju glede podatkovnih tipov in podobnega.


Primer uporabe z dvema int spremenljivkama:
Koda: Izberi vse
   int a = 10, b = 20;    //Ustvari dve spremenljivki a in b in jim nastavi osnovni vrednosti
   int* PTR;              //Ustvari spremenljivko PTR.
   int** PTR2PTR;         //PTR2PTR za upravljanje z PTR spremenljivko.

   //Nastavi lokacijo, kamor kaže PTR2PTR
   PTR2PTR = &PTR;        //Shrani lokacijo PTR spremenljivke v PTR2PTR

   //Nastavi lokacijo, kamor bo kazal pointer, na katerega kaže spremenljivka PTR2PTR
   *PTR2PTR = &a;         //Preko PTR2PTR spremenljivke določi, kamor bo kazala spremenljivka PTR

   //Izpisi vrednost iz lokacije, kamor kaze spremenljivka PTR
   //V našem primeru je to lokacija spremenljivke a (&a)
   printf("Vrednost *ptr = %d\r\n", *PTR);

   //Povej pointerju, naj kaze na spremenljivko b
   *PTR2PTR = &b;         //Preko PTR2PTR spremenljivke določi, kamor bo kazala spremenljivka PTR

   //Izpisi vrednost iz lokacije, kamor kaze spremenljivka PTR
   //V našem primeru je to lokacija spremenljivke b (&b)
   printf("Vrednost *ptr = %d\r\n", *PTR);


Zgornja koda je enaka spodnji:
Koda: Izberi vse
   int a = 10, b = 20;    //Ustvari dve spremenljivki a in b in jim nastavi osnovni vrednosti
   int* PTR;              //Ustvari spremenljivko PTR.

   PTR = &a;
   printf("Vrednost *ptr = %d\r\n", *PTR);

   PTR = &b;
   printf("Vrednost *ptr = %d\r\n", *PTR);


Druga koda je precej lepša in krajša. Zakaj torej uporabljati dvojni pointer?
Z uporabo dvojnega pointerja lahko iz nekje drugje (recimo iz funkcije) spremeniš, kam bo po novem kazal pointer, kar z enojnim pointerjem ni možno.

Meni do sedaj najbolj uporabna situacija za dvojne pointerje je bila sledeča:
  • Imam string nekje v pomnilniku, ter imam pointer, ki kaže na njega.
  • Kličem funkcijo, v kateri preverim, če je v tem string spredaj neka koda, ignoriraj to kodo in povej pointerju, naj bo začetek stringa tako, ko se koda konča.
  • Pointerja s funkcijo ni mogoče spreminjati, razen če je pointer globalna spremenljivka, kar pa je izven zaželjenega. Zato se uporabi PTR2PTR za reševanje problema.

Primer:
Koda: Izberi vse
//Deklaracija string v pomnilniku
char str[] = "[KODA]To je moj string.";

//Funkcija, ki modificira naš osnovni pointer in mu spremeni lokacijo kamor kaže
//Funkcija prejme kopijo lokacije pointerja. Vsaka sprememba "ptr" spremenljivke znotraj te funkcije na izven ne bo imela efekta.
void modificiraj_string(char** ptr2ptr) {
   //Povečaj pointer za eno mesto, odrezi prvo mesto v stringu
   //Povecaj lokacijo, kamor kaže osnovni pointer.
   //Z *ptr dostopaš do lokacije, na katero kaže osnovni pointer, katerega lokacijo smo podali tej funkciji.
   if (strncmp(*ptr2ptr, "[KODA]", 6) == 0) {
      //Premakni lokacijo, kamor kaže vhodni pointer za 6 mest (ignoriraj "[KODA]" del v stringu)
      (*ptr2ptr) += 6;
   }

   //Spodnji ukaz poveča le lokalno spremenljivko ptr2ptr, in ne originalno, ker je vsebina podana kot kopija naslova in ne referenca
   ptr2ptr++; //Sprememba navzven te funkcije ni vidna in nima vpliva
}

int main() {
   //Ustvari pointer, ki kaže na string
   char* ptr = str;

   //Izpisi string s pomočjo pointerja
   printf("Original string: %s\r\n", ptr);

   //Modificiraj string in pošlji lokacijo, kjer je shranjen naš pointer, ki že nekam kaže
   modificiraj_string(&ptr);

   //Ponovno Izpisi string s pomočjo pointerja
   printf("Modificiran string: %s\r\n", ptr);

   return 0;
}


PTR2PTR sistem se lahko uporablja tudi, kadar imamo polje pointerjev, recimo:
Koda: Izberi vse
//Polje 10 pointerjev
int* polje[10];
//Dvojni pointer, ki kaže na prvi element v polju = prvi pointer
int ** PTR2PTR = polje;


Debata tukaj
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255

Re: Mala šola C jezika - Kazalci

OdgovorNapisal/-a tilz0R » 16 Okt 2016, 15:36

12. Zaključek

S tem se mala šola C jezika in pointerjev zaključuje.

Obisk je bil zelo porazen, tukaj sta 2 možna razloga za to:
- Tisti, ki so že napisali prvo funkcijo, uporabili printf, tudi vedo pointerje.
- Preslaba razlaga za vse ostale.

Kakorkoli, sam sem zadovoljen, da sem to napisal, mogoče bo komu kdaj prišlo prav.
Skupaj iščimo razloge tukaj.
Knowledge sharing is people' caring., T. MAJERLE
Uporabniški avatar
tilz0R
 
Prispevkov: 1829
Pridružen: 18 Jan 2015, 00:12
Kraj: Črnomelj
Zahvalil se je: 230 krat
Prejel zahvalo: 517 krat
Uporabnika povabil: s56rga
Število neizkoriščenih povabil: 255


Vrni se na Programski jeziki

Kdo je na strani

Po forumu brska: 0 registriranih uporabnikov in 0 gostov