Diferență între revizuiri ale paginii „C++ POO Lab Lucrarea 1”

De la WikiLabs
Jump to navigationJump to search
 
(Nu s-au afișat 31 de versiuni intermediare efectuate de alți 3 utilizatori)
Linia 1: Linia 1:
În acest laborator ne vom reaminti sintaxa limbajului C, vom exersa utilizarea mediului de dezvoltare Netbeans și a ''debugger''-ului.
+
= Obiective : recapitulare structuri si pointeri =
  
== Tool-ul de depanare GDB ==
+
La sfârșitul acestei recapitulări studenții vor fi capabili:
Depanatorul GNU, cunoscut drept GDB (GNU debugger), este depanatorul standard pentru sistemul de software GNU.<br>
+
* definească tipuri de date de tip struct;
Scopul unui depanator precum GDB este de a permite utilizatorului vadă ce se întâmplă în interiorul unui alt program în timp ce acesta se execută sau ce s-a întâmplat cu programul în momentul în care acesta a dat crash.<br>
+
* să declare și utilizele variabile de tip struct în programe;
GDB este capabil de a face 4 mari categorii de operații (și alte tipuri de operații ce duc la îndeplinirea celor 4):
 
* să pornească programul, specificând orice ar putea interveni în buna funcționare a acestuia;
 
* să facă programul se oprească din execuție în anumite condiții specificate de utilizator;
 
* să examineze ce s-a întâmplat în momentul opririi programului;
 
* să schimbe anumite lucruri în program pentru a putea corecta efectele unui bug și a investiga urmările altuia.<br>
 
Odată pornit, GDB citește comenzi din terminal până la întâlnirea comenzii de ieșire "quit".<br>
 
<div class="regula">'''<font color="red"> Atenție:</font>''' Pentru a putea folosi debugger-ul, la generarea fișierului executabil trebuie folosită opțiunea '''-g''', care adaugă simboluri de debug în executabil, fără de care depanarea nu este posibilă. (vezi [http://wiki.dcae.pub.ro/index.php/PC_Laborator_1 Laboratorul 1])</div>
 
Exemplul unei comenzi de compilare în vederea depanării:<br>
 
  
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/Desktop $</span> gcc -g hello.c -o hello'''
+
* să definească și să utilizeze tipuri de date pointer;
 +
* să folosească pointeri pentru a putea modifica variabilele trimise ca argumente unor funcții;
 +
* să aloce, să folosească și să elibereze memorie HEAP, în mod dinamic (in C si C++);
 +
* să utilizeze aritmetica pointerilor pentru a itera peste elemente de la adrese consecutive de memorie;
  
=== Comenzi specifice GDB ===
+
= Structura în C =
Cea mai folosită modalitate de a porni tool-ul de depanare este de a scrie '''gdb''' în terminal, urmat de numele executabilului ce se dorește a fi depanat.
 
  
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/Desktop $</span> gdb hello'''
+
Tipurile de date <code>struct</code> sunt utilizate pentru a agrega mai multe multe varibile care au sens împreună. De exemplu, dorim să stocăm informații despre o mașină, prin urmare avem nevoie să stocăm marca, modelul, anul de fabricație, numărul de înmatriculare, culoarea, etc. Putem în acest caz să definim o structură numită ''Masina'' care să stocheze aceste valori. Variabilele care aparțin unei structuri se numesc '''câmpuri''' ale structurii. Un exemplu:
  
O parte din comenzile cele mai utilizate ale GDB sunt următoarele (pentru lista completă studiați pagina de manual GDB - man gdb):
+
<syntaxhighlight lang="c">
{| class="wikitable"
+
struct Masina {
! Opțiune !! Efect
+
    char marca[100];
|-
+
    char model[50];
| <code style="color: green">break</code> [nume_fișier:] nume_funcție <br> <code style="color: green">break</code> [nume_fișier:] număr_linie || Setează un breakpoint (punct de întrerupere) la începutul funcției ''nume_funcție'' sau la linia specificată prin ''număr_linie'' din fișierul specificat prin ''nume_fișier''.
+
    unsigned short anFabricatie;
|-
+
    char numarInmatriculare[8];
| <code style="color: green">run</code> [listă_argumente] || Pornește programul în execuție (cu lista de argumente, dacă au fost specificate).
+
    char culoare[10];
|-
+
};
| <code style="color: green">bt</code> || Backtrace: afișează stiva de program.
+
</syntaxhighlight>
|-
 
| <code style="color: green">print</code> expresie || Afișează valoarea unei expresii (valoarea stocată într-o variabilă, valoarea returnată de o funcție etc.).
 
|-
 
| <code style="color: green">c</code> || Continuă execuția programului (după ce a fost oprit, spre exemplu după un breakpoint).
 
|-
 
| <code style="color: green">next</code> || Execută următoarea linie de program (după oprire), sărind peste orice apel de funcție care se găsește în această linie.
 
|-
 
| <code style="color: green">list</code> [nume_fișier:] nume_funcție || Afișează liniile de cod ale programului din vecinătatea locului unde este oprit acum.
 
|-
 
| <code style="color: green">step</code> || Execută următoarea linie de program (după oprire), intrând în orice funcție apelată de acea linie.
 
|-
 
| <code style="color: green">help</code> [nume] || Afișează informații despre comanda GDB ''nume'' sau informații generale despre utilizarea GDB.
 
|-
 
| <code style="color: green">quit</code> || Iese din GDB.
 
|}
 
  
<div class="regula"> ''' Observație: ''' Cele mai multe dintre aceste comenzi pot fi apelate doar prin prima literă a numelui lor. </div>
+
Câmpurile structurii ''Masina'' sunt: '''marca''', '''model''', '''anFabricatie''', '''numarInmatriculare''' și '''culoare'''.
Spre exemplu, următoarea comandă:
 
  '''(gdb) next'''
 
este identică cu:
 
  '''(gdb) n'''
 
<div class="regula"> ''' Observație: ''' Dacă se apasă pe enter fără a scrie o comandă, se repetă ultima comandă specificată.</div>
 
  
=== Exemplu de depanare ===
+
<span style="color: red; font-weight: bold">Atenție</span>: Definiția unei structuri nu implică automat și existența unei variabile de tipul respectiv, așa cum definirea tipului de date <code>int</code> nu implică existența unei varibile de tip <code>int</code>.
Se dă fișierul sursă ''suma_fact.c'', care conține următoarele instrucțiuni:
+
 
<syntaxhighlight lang="C">
+
Declararea unei variabile de tipul ''Masina'' se face exact ca declararea oricărei alte variabile, sub forma: <tip_data> <nume_variabila>, cu observația că tipul de dată va conține și cuvântul cheie <code>struct</code>, deci acesta va fi <code>struct Masina</code>:
#include<stdio.h>
+
 
 +
<syntaxhighlight lang="c">
 +
struct Masina {
 +
    char marca[100];
 +
    char model[50];
 +
    unsigned short anFabricatie;
 +
    char numarInmatriculare[8];
 +
    char culoare[10];
 +
};
  
unsigned int factorial(int n) {
+
int main() {
     if (n == 0) {
+
     struct Masina masina;
        return 1;
+
     return 0;
     }
 
    int i;
 
    unsigned int fact = 0;
 
    for (i = 1; i <= n; i++){
 
        fact *= i;
 
    }
 
    return fact;
 
 
}
 
}
 +
</syntaxhighlight>
  
 +
Odată definită o variabilă de tip <code>struct</code>, operatorul folosit pentru a accesa câmpurile structurii este <code>.</code>:
 +
 +
<syntaxhighlight lang="c">
 +
#include <stdio.h>
 +
 +
struct Masina {
 +
    char marca[100];
 +
    char model[50];
 +
    unsigned short anFabricatie;
 +
    char numarInmatriculare[8];
 +
    char culoare[10];
 +
};
  
 
int main() {
 
int main() {
     int n, i;
+
     struct Masina masina_mea;
     do {
+
     printf("Care este marca masinii? ");
        printf("Introduceti un numar natural mai mic sau egal cu 8: ");
+
    fgets(masina_mea.marca, 100, stdin);
        scanf("%d", &n);
+
    printf("Care este modelul masinii? ");
     } while (n < 0 || n > 8);
+
    fgets(masina_mea.model, 50, stdin);
     unsigned int suma = 0;
+
     printf("Care este numarul de inmatriculare al masinii? ");
     for (i = 0; i <= n; i++) {
+
     fgets(masina_mea.numarInmatriculare, 8, stdin);
        suma += factorial(i);
+
     printf("Care este culoarea masinii? ");
     }
+
    fgets(masina_mea.culoare, 10, stdin);
     printf("%u\n", suma);
+
     printf("Care este anul de fabricatie a masinii? ");
 +
     scanf("%hu", &masina_mea.anFabricatie);
 +
 
 
     return 0;
 
     return 0;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Programul își propune să calculeze suma factorialelor de la 0 la n, unde n este introdus de la tastatură. Dacă rulăm programul vom obține ca rezultat numărul '''1''', indiferent de ce a fost introdus de la tastatură. Ne propunem să descoperim bug-ul folosind GDB.<br>
 
Compilăm sursa, adăugând simboluri de debug în executabil, după care lansăm în execuție debugger-ul:
 
  
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/Desktop $</span> gcc -g suma_fact.c -o suma_fact'''
+
  <span style="color: blue; font-weight: bold">Observație</span>: O structură poate avea câmpuri de orice tip, inclusiv de tipul altor structuri.
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/Desktop $</span> gdb suma_fact'''
 
  
<div class="regula">'''<font color="red"> Atenție:</font>''' Comanda de mai sus lansează în execuție numai debugger-ul, nu și programul ''suma_fact''.</div>
+
Un tip de dată de tip structură poate fi folosit ca orice alt tip de dată, spre exemplu pentru a crea vectori de acel tip, dar și pentru a defini funcții care au argumente sau întorc valori de acel tip:
<div class="regula"> ''' Observație: ''' Putem observa că am pornit debugger-ul prin faptul că fiecare rând nou din linia de comandă începe acum astfel:</div>
 
  '''(gdb)'''
 
Punem un ''breakpoint'' la linia 20, unde se citește valoarea lui n:
 
  '''(gdb) b 20'''
 
  '''Breakpoint 1 at 0x80484c2: file suma_fact.c, line 20.'''
 
  '''(gdb) '''
 
  
Pornim programul în interiorul debugger-ului:
+
<syntaxhighlight lang="c">
  '''(gdb) run'''
+
#include <stdio.h>
  '''Starting program: /home/student/Desktop/suma_fact'''
 
  ''' '''
 
  '''Breakpoint 1, main () at suma_fact.c:20'''
 
  '''20         scanf("%d", &n);'''
 
  '''(gdb) '''
 
Trecem mai departe și introducem o valoare pentru n:
 
  '''(gdb) next'''
 
  '''Introduceti un numar natural mai mic sau egal cu 8: 6 '''
 
  '''21     }while(n<0 || n>8);'''
 
  '''(gdb) '''
 
Vom parcurge programul pas cu pas până la apelarea funcției ''factorial'':
 
  '''(gdb) n'''
 
  '''22     unsigned int suma = 0;'''
 
  '''(gdb) n'''
 
  '''23     for(i=0; i<=n; i++){'''
 
  '''(gdb) n'''
 
  '''24         suma += factorial(i);'''
 
  '''(gdb) s'''
 
  '''factorial (n=0) at suma_fact.c:4'''
 
  '''4     if(n==0){'''
 
  '''(gdb) s'''
 
  '''5         return 1;'''
 
  '''(gdb) s'''
 
  '''13 }'''
 
  '''(gdb) s'''
 
  '''main () at suma_fact.c:23'''
 
  '''23     for(i=0; i<=n; i++){'''
 
  '''(gdb) s'''
 
  '''24         suma += factorial(i);'''
 
  '''(gdb) s'''
 
  '''factorial (n=1) at suma_fact.c:4'''
 
  '''4     if(n==0){'''
 
  '''(gdb) s'''
 
  '''8     unsigned int fact=0;'''
 
  '''(gdb) s'''
 
  '''9     for(i=1; i<=n; i++){'''
 
  '''(gdb) s'''
 
  '''10         fact *= i;'''
 
  '''(gdb) s'''
 
  '''9     for(i=1; i<=n; i++){'''
 
  '''(gdb) s'''
 
  '''12     return fact;'''
 
  '''(gdb) print fact'''
 
  '''$1 = 0'''
 
  '''(gdb) '''
 
Observăm că valoarea returnată de funcția ''factorial'' pentru orice număr nenul este '''0'''. Acest lucru se datorează inițializării variabilei ''fact'' cu valoarea '''0''' la începutul funcției ''factorial''. Putem ieși din debugger pentru a modifica linia 8 din fișierul sursă suma_fact.c:
 
  '''(gdb) q'''
 
  '''A debugging session is active.'''
 
  ''' '''
 
  '''  Inferior 1 [process 4279] will be killed.'''
 
  ''' '''
 
  '''Quit anyway? (y or n) y '''
 
  '''<span style="color: green">student@pracsis01</span> <span style="color: blue">~/Desktop $</span>'''
 
<div class="regula"> ''' Observație: ''' Pentru a putea intra în interiorul funcției ''factorial'' este absolut necesar să se folosească comanda '''step''' și nu comanda '''next''' care ar trece peste acea linie 24 fără a intra în corpul funcției ''factorial''.</div>
 
  
După acest laborator veți putea folosi IDE-ul Netbeans pentru a scrie și depana programe în C. Totodată veți relua și recapitula noțiunile legate de pointeri în C.
+
struct Masina {
 +
    char marca[100];
 +
    char model[50];
 +
    unsigned short anFabricatie;
 +
    char numarInmatriculare[8];
 +
    char culoare[10];
 +
};
  
= Utilizarea IDE-ului Netbeans =
+
struct Masina citesteMasina() {
 +
    struct Masina masina_mea;
 +
    printf("Care este marca masinii? ");
 +
    fgets(masina_mea.marca, 100, stdin);
 +
    printf("Care este modelul masinii? ");
 +
    fgets(masina_mea.model, 50, stdin);
 +
    printf("Care este numarul de inmatriculare al masinii? ");
 +
    fgets(masina_mea.numarInmatriculare, 8, stdin);
 +
    printf("Care este culoarea masinii? ");
 +
    fgets(masina_mea.culoare, 10, stdin);
 +
    printf("Care este anul de fabricatie a masinii? ");
 +
    scanf("%hu", &masina_mea.anFabricatie);
 +
    return masina_mea;
 +
}
  
[https://netbeans.org/ Netbeans] este un mediu integrat de dezvoltare (IDE) open-source și gratuit care permite dezvoltarea de programe în limbajele Java, C, C++, Javascript, HTML, PHP și Groovy. Netbeans nu instalează și compilatoare pentru Java și C/C++, acestea trebuie instalate manual, în prealabil.  
+
void afiseazaMasina(struct Masina masina){
 +
    printf("Masina marca %s si modelul %s are numarul de inmatriculare "
 +
            "%s, culoarea %s si a fost fabricata in anul %hu!\n",
 +
            masina.marca, masina.model, masina.numarInmatriculare,
 +
            masina.culoare, masina.anFabricatie);
 +
}
  
<div class="regula"><span style="color: red; font-weight: bold">Atenție:</span> Imaginea de Linux folosită la Programarea Calculatoarelor are deja instalat compilatorul de C.</div>
+
int main() {
 +
    struct Masina parcAuto[10];
 +
    parcAuto[0] = citesteMasina();
  
== Instalarea GCC ==
+
    afiseazaMasina(parcAuto[0]);
  
=== Instalarea GCC în Linux ===
+
    return 0;
 +
}
 +
</syntaxhighlight>
  
Pentru instalarea compilatorului de C (GCC) în distribuțiile de Linux provenite din Ubuntu (Ubuntu, Kubuntu, Xubuntu, Mint, LMDE, etc.) se poate folosi comanda:
 
  
apt-get install -y build-essential
+
Plecând de la limbajul C, ne amintim că acesta pune la dispoziție o serie de tipuri de date primitive, incluse în standardul limbajului. Ca exemple, avem: ''int'', ''long'', ''char'', ''double'', etc. În plus, există posibilitatea de a crea structuri compuse folosind cuvântul cheie ''[http://en.wikipedia.org/wiki/Struct_(C_programming_language) struct]''. O structură în C este compusă din una sau mai multe variabile care pot fi ori de tip primitiv, ori alte structuri, ori o combinație din cele două.
  
=== Instalarea GCC în Windows ===
 
  
Pentru compilarea de programe cu GCC în Windows, aveți nevoie de instalarea acestui compilator care poate fi făcută prin instalarea unuia din următoarele două suite de programe:
+
În continuare avem un exemplu de definiție a unor structuri în C.
* [https://cygwin.com Cygwin] - Tutorial de instalare [https://cygwin.com/install.html aici].
+
<syntaxhighlight lang="C">
* [http://www.mingw.org/ MinGW] - Tutorial de instalare [http://www.mingw.org/wiki/InstallationHOWTOforMinGW aici].
+
struct mystring{
 +
    char* str;
 +
    unsigned length;
 +
};
  
<div class="regula"><span style="color: red; font-weight: bold">Atenție:</span> Nu uitați ca la instalare să bifați în lista de pachete și compilatorul de C (<code>gcc</code>), <code>make</code> și <code>git</code>.</div>
+
struct person{
 +
    struct mystring *first_name;
 +
    struct mystring *last_name;
 +
    unsigned age;
 +
    float height;
 +
    float weight;
 +
};
 +
</syntaxhighlight>
  
== Instalarea Netbeans ==
+
Se vede că structura ''person'' conține pointeri la două structuri de tip ''mystring''. Relația este descrisă de schema bloc următoare (cu exemple de valori pentru variabilele primitive):
  
De la adresa https://netbeans.org/downloads/ puteți descărca kitul de instalare pentru sistemul vostru de operare (imaginea de Virtualbox cu Linux folosită la Programarea Calculatoarelor ruelază un Linux pe 32 de biți). Descărcați una din variantele care suporta C/C++ ('''C/C++''' sau '''All''').
+
<syntaxhighlight lang="text">
 +
    struct person
 +
-------------------------                                          char char ...                     
 +
| struct mystring*    ● |------>      struct mystring            --------------------------------------
 +
-------------------------          -------------------------      | G | h | e | o | r | g | h | e | \0 |
 +
| struct mystring*    ● |---      | char*              ● | ----> --------------------------------------
 +
-------------------------  |      -------------------------
 +
| unsigned          29 |  |      | unsigned          10 |
 +
-------------------------  |      -------------------------
 +
| float            1.7 |  |
 +
-------------------------  |                                      char char ...
 +
| float            68.9 |  ---->      struct mystring              ------------------------------
 +
-------------------------          -------------------------      | V | a | s | i | l | e | \0 |
 +
                                  | char*              ● |---->  ------------------------------
 +
                                  -------------------------
 +
                                  | unsigned          10 |
 +
                                  -------------------------
 +
</syntaxhighlight>
  
=== Instalare Netbeans în Linux ===
+
În continuare, vom da un exemplu de utilizare a acestor structuri:
 +
<syntaxhighlight lang="C">
 +
#include<stdio.h>
 +
#include<stdlib.h>
 +
#include<string.h>
 +
 
 +
int main(){
 +
    //make a default mystring with no content and length = 30
 +
    struct mystring * some_string = (struct mystring*)malloc(sizeof(struct mystring));
 +
    unsigned default_length = 30;
 +
    some_string->length = default_length;
 +
    some_string->str = (char*)malloc(default_length * sizeof(char));
 +
    strcpy(some_string->str, "Vasile");
 +
 
 +
    //make a person structure in which all strings refer to the default empty mystring
 +
    struct person* new_person = (struct person*)malloc(sizeof(struct person));
 +
    new_person->first_name = some_string;
 +
    new_person->last_name = some_string;
 +
    new_person->age = 29;
 +
    new_person->height = (float)1.7;
 +
    new_person->weight = 68.9F;
  
Fișierul descărcat pentru Linux va avea extensia '''.sh'''. Pentru a instala programul, deschideți un terminal și rulați următoarele comenzi:
+
    printf("Persoana se numeste %s %s, are varsta de %d ani, inaltimea %f si greutatea %f\n", new_person->first_name->str,
 +
        new_person->last_name->str,
 +
        new_person->age,
 +
        new_person->height,
 +
        new_person->weight);
  
<syntaxhighlight lang="bash">
+
    return 0;
cd ~/Downloads # navigare catre directorul unde a fost salvat fisierul descarcat
+
}
chmod +x *.sh # activarea fanionului 'executabil' pentru toate fisierele cu extensia .sh
 
sudo ./netbeans-8.1-cpp-linux-x86.sh # folositi numele corespunzator al fisierului descarcat
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Mai departe urmați pașii din programul de instalare.
+
În acest exemplu, schema bloc este diferită dintr-un punct de vedere esențial: ambii pointeri de tip mystring sunt referință la aceeași adresă, respectiv la același mystring. Astfel, dacă se modifică ''new_person->first_name'', atunci implicit se modifică și ''new_person->last_name'' (de fapt este aceeași structură):
  
=== Instalare Netbeans în Windows ===
+
<syntaxhighlight lang="text">
 +
-------------------------
 +
| struct person        |
 +
-------------------------        -------------------------      char char...            char ...
 +
| struct mystring*    ● |------>  | struct mystring      |      ------------------------------------------------
 +
-------------------------    /    -------------------------      | V | a | s | i | l | e | \0 |    |  ...  |  |
 +
| struct mystring*    ● |---/    | char*              ● |-----> -----------------------------------------------
 +
-------------------------        -------------------------
 +
| unsigned          29 |        | unsigned          30 |
 +
-------------------------        -------------------------
 +
| float            1.7 | 
 +
------------------------- 
 +
| float            68.9 |   
 +
-------------------------
  
Fișierul descărcat va avea extensia '''.exe'''. Rulați-l și urmăriți instrucțiunile. Veți fi întrebați unde este compilatorul de C/C++. Executabilele pentru acestea sunt în directorul unde ați instalat MinGW sau Cygwin, în subdirectorul <code>/usr/bin</code>.
+
</syntaxhighlight>
  
== Realizarea unui proiect ==
+
Pentru a face un rezumat, structura, in C, este un '''tip de dată''' compusă din tipuri primitive, sau alte structuri. Analog cu orice alt tip de dată, se pot defini variabile de tipul structurii, așa cum se pot defini variabile de tip primitiv. Limitarea fundamentală a structurilor este că acestea nu pot conține decât '''date''', nu și '''funcții'''. În laboratorul următor vom vedea aceasta limitare rezolvată prin noțiunea de '''clasă'''.
  
Aplicațiile în Netbeans sunt structurate în '''Proiecte'''. O aplicație este un proiect. Nu este suficient să scrieți un fișier sursă C și să-l rulați. Netbeans trebuie să creeze fișiere '''Makefile''' și să pregătească mediul pentru compilare și execuție precum și să fie capabil să vă ofere informații legate de corectitudinea codului și să vă ofere sugestii de îmbunătățire. Pentru a crea un proiect nou, se folosește meniul <code>File - New Project... </code> sau combinația <code>Ctrl-Shift-n</code>:
+
= Tipuri de date pointer =
  
[[Fișier:netbeans_new_project.png|1000px|Proiect nou]]
+
Un '''pointer''' reprezintă o variabilă care stochează o adresă în memoria dedicată aplicației. Tipul variabilei de tip pointer specifică tipul datei care poate fi citit de la adresa respectivă.
  
Mai departe selectați categoria '''C/C++''' și la '''Project''' selectați '''C/C++ Application'''. Va apărea următoarea fereastră:
+
O variabilă de tip pointer se definește în felul următor:
  
[[Fișier:netbeans_new_application.png|Aplicație nouă]]
+
<tip_data> * <nume_variabila>;
  
Completați următoarele informații:
+
Spre exemplu:
* '''Project Name''' - numele proiectului/ al aplicației voastre; va fi numele directorului creat de Netbeans pentru proiect;
 
* '''Project Location''' - directorul unde va fi salvat directorul proiectului de către Netbeans;
 
* '''Project Folder''' - calea completă până la directorul proiectului; este obținut din concatenarea celor două de mai sus; nu poate fi modificat;
 
* '''Project Makefile Name''' - numele fișierului Makefile care va fi creat de către Netbeans pentru proiectul vostru;
 
* '''Create Main File''' - se bifează dacă se dorește crearea unui fișier sursă care să conțină funcția '''main''' și se completează numele acestui fișier (fără extensie); în căsuța de selecție din dreapta se alege limabjul (C sau C++) și standardul folosit pentru compilarea codului și generarea fișierului main;
 
* '''Build Host''' - numele sau adresa IP a calculatorului unde se realizează compilarea ('''localhost''' este calculatorul pe care lucrați);
 
* '''Tool Collection''' - dacă există mai multe compilatoare instalate, de aici se alege care compilator este utilizat pentru proiect.
 
  
După ce dați click pe '''Finish''', proiectul se va deschide și fereastra se va schimba în modul de editare.
+
<syntaxhighlight lang="c">
 +
int * pa;
 +
</syntaxhighlight>
  
== Componentele IDE-ului Netbeans ==
+
Variabila de tip pointer <code>pa</code> nu memorează un întreg, ci o adresă în memorie, iar de la adresa respectivă se poate citi un întreg. Acest lucru se numește indirectare simplă. În plus, deoarece tipul pointer este în sine un tip de dată, și în definiția unui pointer <tip_data> poate fi un pointer, aceasta permite următoarele construcții:
 +
* <code>int * pa;</code> - variabilă ce stochează o adresă de unde se poate citi un întreg (indirectare simplă);
 +
* <code>int ** pa;</code> - variabilă ce stochează o adresă de unde se poate citi o adresă de unde se poate citi un întreg (dublă indirectare);
 +
* <code>int *** pa;</code> - variabilă ce stochează o adresă de unde se poate citi o adresă de unde se poate citi o adresă de unde se poate citi un întreg (triplă indirectare);
 +
* etc.
  
[[Fișier:netbeans_env.png|1000px|Netbeans]]
+
<div class="regula"><span style="color: red; font-weight: bold">Regulă:</span> Orice variabilă este stocată în memorie și deci are o adresă în memorie.</div>
  
Zonele din imagine sunt:
+
În C există un tip de dată pointer care poate memora o adresă fără a ști ce date se află la adresa respectivă. Acest tip de pointer este <code>void*</code>.
  
# Zona <span style="color: gold">gabenă</span> - bara de instrumente - conține butoane folosite pentru acces rapid la funcționalitatea cea mai utilizată (compilare, rulare, etc.) și conține:
+
== Dimensiunea tipului de date pointer ==
#* selecția tipului de compilare ('''Debug''' sau '''Release''') are efect asupra nivelului de optimizare comandat compilatorului și selectează sau nu facă simbolurile de debug sunt adăugate executabilului; pe scurt, nu se poate face debug pe executabilul de Release, dar executabilul de Debug este mai lent decât cel de Release;
 
#* testare în browser, este accesibil doar pentru aplicații web (HTML, PHP, Javascript, etc.), nu și pentru aplicații C/C++;
 
#* '''Build Project''' - se apelează '''make all''' pentru compilarea programului;
 
#* '''Clean and Build Project''' - se apelează '''make clean all''' pentru ștergerea binarelor și apoi recompilarea programului;
 
#* '''Run Project''' - se rulează executabilul generat de build (dacă acesta nu există sau nu este actualizat, se rulează automat '''Build Project''' înainte);
 
#* '''Debug Project''' - se rulează executabilul generat de build în modul de debug (cu ajutorul GDB, vezi [[PC Laborator 10#Tool-ul de depanare GDB]]), permițând plasarea de breakpoints, execuție pas cu pas, vizualizarea valorilor variabilelor, etc.
 
#* '''Profile Project''' - indisponibil în C/C++, permite vizualizarea resurselor consumate și ajută la optimizarea programului.
 
# Zona <span style="color: blue">albastră</span> - conține 4 tab-uri din care importante pentru proiectele în C sunt primele două: '''Projects''' și '''Files'''.
 
#* '''Projects''' - arată proiectele deschise și tipul lor; în cazul de față este un singur proiect deschis - '''TestProjectHelloWorld'''; sub el se află 5 categorii în care sunt plasate fișierele care fac parte din proiect:
 
#** ''Header Files'' - fișierele header (cu extensia .h);
 
#** ''Resource Files'' - diferite fișiere cu resurse folosite de aplicație (fișiere de configurare, xml, imagini, etc.);
 
#** ''Source Files'' - fișiere sursă C (cu extensia .c);
 
#** ''Test Files'' - fișiere de test pentru funcțiile din fișierele sursă;
 
#** ''Important Files'' - fișiere importante pentru proiect (cum ar fi fișierul Makefile).
 
#* '''Files''' - se poate vedea directorul proiectului cu fișierele și subdirectoarele sale; în directorul proiectului se realizează implicit următoarele subdirectoare:
 
#** ''build'' - conține fișierele obiect pentru fiecare din surse, pentru fiecare tip de compilare ('''Debug''' sau '''Release''');
 
#** ''dist'' - conține fișierele executabile pentru fiecare tip de compilare ('''Debug''' sau '''Release''');
 
#** ''nbproject'' - conține fișiere de configurare pentru proiect.
 
# Zona <span style="color: green">verde</span> - conține câte un tab pentru fiecare fișier deschis pentru compilare; implicit bara verde se află deasupra ferestrei de editare (portocaliu) dar poate fi mutată.
 
# Zona <span style="color: orange">portocalie</span> - fereastra de editare a codului ce conține pe bara de sus butoane pentru navigarea rapidă prin fișierele deschise și / sau modificate recent; de remarcat aici este butonul '''History''' care, dacă fișierul este salvat într-un sistem de ''revision control'' (cum ar fi Git), permite vizualizarea istoriei modificărilor fișierului.
 
# Zona <span style="color: hotpink">roz</span> - listează elementele definite în fișierul deschis în zona portocalie; dacă fișierul este un header sau un fișier sursă C, aici se vor vedea tipurile de date definite, variabilele globale, funcțiile cu argumentele lor, macrourile de preprocesor, etc.; dacă în schimb este deschis un Makefile, se vor vedea toate rețetele definite în Makefile-ul respectiv;
 
# Zona <span style="color: red">roșie</span> - zona de ''Output'' reprezintă consola unde se afișează rezultatele compilării cât și ieșirea generată de program în timpul execuției, pe ambele stream-uri (stdout și stderr).
 
  
== Exemplu de program ==
+
Conform regulii de mai sus, și variabilele de tip pointer ocupă loc în memorie, deci au dimensiune, în octeți. Un pointer nu oferă informații legate de dimensiunea ocupată de datele de la adresa respectivă, ci doar adresa de unde începe zona ocupată. Din moment ce un pointer memorează doar o adresă, dimensiunea variabilelor de tip pointer nu depinde decât de dimensiunea spațiului de memorie. Astfel, pentru procesoare și sisteme de operare pe 32 de biți, o variabilă de tip pointer va avea 32 de biți, iar pe procesoare și sisteme de operare pe 64 de biți, o variabilă de tip pointer va avea 64 de biți.
 +
 
 +
== Adresa unei variabile ==
 +
 
 +
Operatorul care permite aflarea adresei unde este stocată o variabilă este ampersand (&). Acesta este un operator unar ce se plasează înaintea unei variabile iar rezultatul evaluării sale este adresa unde este stocată variabila respectivă. Această adresă poate fi stocată într-o altă variabilă de tipul corespunzător. Altfel spus, pentru o variabilă de tip ''tip_data'', adresa acesteia se poate stoca într-o variabilă de tip ''tip_data *'':
 +
<syntaxhighlight lang="c">
 +
float floatValue;
 +
float * floatAddress = &floatValue;
 +
 
 +
int intValue;
 +
int * intAddress = &intValue;
 +
int ** intPointerAddress = &intAddress;
 +
</syntaxhighlight>
 +
 
 +
== Adresa NULL ==
 +
 
 +
Pentru orice aplicație, adresa 0 din spațiul ei de memorie este rezervată. Aceasta nu poate fi nici scrisă și nici citită. Această adresa poartă numele de '''NULL'''. Orice variabilă de tip pointer poate lua valoarea '''NULL''', lucru care de obicei specifică faptul că de fapt variabila pointer nu stochează o adresă validă.
 +
 
 +
Constanta '''NULL''' este definită în fișierul header <code style="color: green">stdlib.h</code> (Standard Library).
  
Copiați codul de mai jos înlocuind programul deja existent în fișierul ''main.c'':
 
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 +
#include <stdlib.h>
 +
 +
int main() {
 +
    char * charPointer = NULL;
 +
    return 0;
 +
}
 +
</syntaxhighlight>
 +
 +
== Valoarea de la o adresă ==
 +
 +
Având o variabilă de tip pointer, valoarea stocată în memorie la adresa respectivă se poate afla folosind caracterul steluță (*), numit și operator de indirectare. Acesta este un operator unar ce se plasează înaintea unei variabile de tip pointer iar rezultatul evaluării sale este valoarea din memorie de la adresa stocată în variabila respectivă. Această valoare poate fi stocată într-o altă variabilă de tipul corespunzător. Altfel spus, pentru o variabilă pointer de tip ''tip_data *'', valoarea de la adresa stocată de pointerul respectiv se poate memora într-o altă variabilă de tip ''tip_data'':
 +
 +
<syntaxhighlight lang="c">
 +
float floatValue;
 +
float * floatAddress = &floatValue;
 +
float anotherFloatValue = *floatAddress;
 +
 +
int intValue;
 +
int * intAddress = &intValue;
 +
int ** intPointerAddress = &intAddress;
 +
int * anotherintAddress = *intPointerAddress;
 +
</syntaxhighlight>
 +
 +
 +
<div class="regula"><span style="color: red; font-weight: bold">Atenție:</span> Accesarea valorii de la o adresă se numește dereferențiere. Dereferențierea unei adrese care nu face parte din spațiul de memorie al aplicației sau dereferențierea lui '''NULL''' va avea ca efect oprirea imediată a programului printr-un ''Segmentation fault.''</div>
 +
 +
<div class="regula"><span style="color: red; font-weight: bold">Atenție:</span> Nu confundați steluța utilizată la definirea unui pointer cu operatorul de indirectare/ dereferențiere. Aceștia sunt la fel de diferiți cum este și operatorul de inmulțire față de cel de indirectare.</div>
 +
 +
= Utilitatea variabilelor de tip pointer =
 +
 +
Pointerii în C au două roluri foarte importante:
 +
# Alocarea dinamică de memorie în HEAP
 +
# Modificarea variabilelor parametri ale unor funcții astfel încât modificarea să se păstreze în afara funcției
 +
 +
== Alocarea dinamică de memorie ==
 +
 +
Memoria alocată unei aplicații de către sistemul de operare este împărțită în mai multe secțiuni, dintre care importante pentru stocarea de date sunt:
 +
 +
* Segmentele BSS și Data - reprezintă memoria alocată pentru variabilele statice (globale), care există de la începutul până la încheierea programului, fără posibilitate de eliberare;
 +
* Stiva (Stack) - zona de memorie în care se alocă contextele funcțiilor apelate în timpul execuției programului și în care se alocă argumentele și variabilele locale are funcțiilor; această zonă este alocată la intrarea în funcție și este eliberată la ieșirea din funcție;
 +
* HEAP - zonă de memorie în care se pot aloca dinamic, de către programator, blocuri de memorie ce pot fi folosite în program până la eliberarea acestora de către programator.
 +
 +
Alocarea și dezalocarea memoriei în heap se fac folosind următoarele funcții:
 +
* <code>void * malloc(unsigned size)</code> - alocă ''size'' octeți într-o zonă continuă din HEAP și întoarce adresa de memorie unde începe zona respectivă; dacă alocarea eșuează (nu exită suficientă memorie într-o zonă continuă în HEAP), funcția va întoarce '''NULL'''; alocarea nu șterge conținutul memoriei respective;
 +
* <code>void * calloc(unsigned elements, unsigned elementSize)</code> - alocă ''elements'' elemente de ''elementSize'' octeți fiecare într-o zonă continuă din HEAP și întoarce adresa de memorie unde începe zona respectivă; dacă alocarea eșuează (nu exită suficientă memorie într-o zonă continuă în HEAP), funcția va întoarce '''NULL'''; alocarea șterge tot conținutul memoriei respective, scriind 0 la fiecare locație;
 +
* <code>void free (void *)</code> - dezalocă o zonă de memorie alocată în prealabil cu <code>malloc</code> sau <code>calloc</code>; apelul succesiv de două sau mai multe ori a funcției <code>free</code> pentru aceeași zonă de memorie sau apelul ei pentru o adresă care nu a fost alocată în prealabil va avea ca efect oprirea imediată a programului cu eroare (''double free or corruption'').
 +
 +
Toate aceste funcții sunt definite în fișierul header <code style="color: green">stdlib.h</code>.
 +
 +
<syntaxhighlight lang="c">
 +
#include <stdlib.h>
 
#include <stdio.h>
 
#include <stdio.h>
  
 
int main(){
 
int main(){
     printf("Hello Netbeans!\n");
+
    int * intPointer;
 +
    short * shortPointer;
 +
 
 +
    intPointer = (int*) malloc(sizeof(int));
 +
    shortPointer = (short*) calloc(1, sizeof(short));
 +
 
 +
     printf("Valoarea din zona alocata pentru int este: %d\n", *intPointer);
 +
    printf("Valoarea din zona alocata pentru short este: %hd\n", *shortPointer);
 +
 
 +
    free(intPointer);
 +
    free(shortPointer);
 +
 
 
     return 0;
 
     return 0;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Dați apoi click pe '''Run Project''' (simbolul verde ''Play'') sau tastați <code>Ctrl+F11</code>:
+
<div class="sfat"><span style="color: red; font-weight: bold">Atenție:</span> Deoarece funcțiile <code>malloc</code> și <code>calloc</code> au doar rolul de a aloca memorie, fără să știe care este scopul utilizării acestei memorii, ele întorc un pointer de tip <code>void *</code>. Astfel, pentru a putea stoca adresa într-un alt tip de pointer (de exemplu <code>int *</code>), ea trebuie convertită la tipul de date corect. Aceasta este explicația prezenței operatorului de cast din fața apelului funcțiilor <code>malloc</code> și <code>calloc</code> din codul de mai sus.</div>
  
[[Fișier:netbeans_run.png|1000px|După execuție]]
+
== Aritmetica pointerilor ==
  
Observați consola din partea de jos a ecranului unde se afișează mesajul "Hello Netbeans" și apoi se semnalează sfârșitul programului raportându-se timpul de execuție.
+
Când se alocă memorie in HEAP, rareori se alocă pentru un singur element, de cele mai multe ori se alocă pentru un număr mare de elemente de același fel. Ca exemplu, dacă vrem să stocăm o imagine High Definition, ne trebuie o zonă de memorie care să poată memora informație de culoare pentru 1920 * 1080 de pixeli, fiecare pixel având informație de culoare pentru roșu, verde și albastru (RGB). Fiecare din aceste componente de culoare se stochează pe un octet ca valoare întreagă fără semn (unsigned char). Prin urmare, pentru un frame se vor aloca 1920 * 1080 * 3 octeți = 6220800, aproape 6 MB. Această memorie se alocă întotdeauna într-o zonă continuă de către funcțiile <code>malloc</code> și <code>calloc</code>:
  
== Exemplu de program în modul ''debug'' ==
+
<syntaxhighlight lang="c">
 +
#include <stdlib.h>
 +
int main(){
 +
    unsigned char * frame;
 +
    frame = (unsigned char*) malloc(1920 * 1080 * 3 * sizeof(unsigned char));
 +
    //... use frame
  
Copiați codul de mai jos înlocuind programul deja existent în fișierul ''main.c'':
+
    free (frame);
<syntaxhighlight lang="C" line>
+
    return 0;
 +
}
 +
</syntaxhighlight>
 +
 
 +
Deoarece pointer-ul nu memorează decât adresa de început a zonei de memorie, există posibilitatea de a modifica adresa pentru a accesa elementele ulterioare. În acest scop, variabilele de tip pointer suportă doar operații aritmetice de adunare sau scădere, nu și de înmulțire sau împărțire.
 +
 
 +
<div class="regula"><span style="color: red; font-weight: bold">Atenție:</span> Incrementarea unui pointer nu crește adresa cu 1, ci cu dimensiunea tipului de dată al pointer-ului. Deci pentru un <code>int * p;</code>, linia <code>p++;</code> va incrementa adresa cu 4 (sizeof(int)):</div>
 +
 
 +
<syntaxhighlight lang="c">
 
#include <stdio.h>
 
#include <stdio.h>
 +
#include <stdlib.h>
 +
 +
int main(){
 +
    int * pointer;
 +
    pointer = (int*) malloc(10 * sizeof(int));
 +
    printf("The pointer address is: %p\n", pointer);
 +
    printf("The pointer address + 1 is: %p\n", pointer + 1);
 +
    free (pointer);
 +
    return 0;
 +
}
 +
</syntaxhighlight>
 +
 +
<div class="regula"><span style="color: red; font-weight: bold">Atenție </span> ca prin operații pe pointeri să nu depășiți zona de memorie alocată.</div>
 +
 +
== Pointerii și vectorii ==
 +
 +
Memoria alocată pentru mai multe elemente de același fel reprezintă de fapt un vector de elemente de acel tip. Astfel aflăm că de fapt vectorii și pointerii, în multe situații se pot folosi interschimbabil.
 +
 +
Când se definește un vector, numele vectorului reprezintă un pointer la adresa de unde începe zona lui de memorie, adică adresa unde este memorat elementul de la indexul 0:
 +
 +
<syntaxhighlight lang="c">
 +
#include <stdio.h>
 +
#include <stdlib.h>
 +
 +
int main(){
 +
    int array[10];
 +
    array[0] = 13;
 +
    printf("The array pointer address is: %p\n", array);
 +
    printf("The value at address %p is %d\n", array, *array);
 +
    return 0;
 +
}
 +
</syntaxhighlight>
  
 +
Dereferențierea unui pointer indexat cu o valoare este echivalentă cu folosirea operatorului de acces la vector: <code>*(v + k)</code> == <code>v[k]</code>, unde '''v''' este un pointer (sau vector) iar '''k''' este un întreg.
 +
 +
<syntaxhighlight lang="c" highlight="14,18">
 +
#include <stdlib.h>
 +
#include <stdio.h>
 
int main(){
 
int main(){
     printf("Debugging program...\n");
+
    int arraySize;
     int value = 0;
+
     printf("size = ");
     value = value + 1;
+
    scanf("%d", &arraySize);
     value++;
+
 
     value = value * 2;
+
     int * heapArray;
     value -= 1;
+
    heapArray = (int*) malloc(arraySize * sizeof(int));
    printf("Value is now %d!\n", value);
+
 
 +
     int i;
 +
     for(i = 0; i < arraySize; i++) {
 +
        printf("heapArray[%d] = ", i);
 +
        scanf("%d", &heapArray[i]);
 +
     }
 +
 
 +
     for(i = 0; i < arraySize; i++) {
 +
        printf("heapArray[%d] = %d\n", i, *(heapArray + i));
 +
    }
 +
 
 +
 
 +
    free (heapArray);
 +
    return 0;
 +
}
 +
</syntaxhighlight>
 +
 
 +
== Pointerii și structurile ==
 +
 
 +
Ca orice tip de dată, și variabilele de tip structură ocupă loc în memorie și deci pot exista pointeri de tip structură:
 +
 
 +
<syntaxhighlight lang="c">
 +
struct Test {
 +
    int intField;
 +
    float floatField;
 +
    char charField;
 +
    char charArrayField[100];
 +
};
 +
 
 +
int main() {
 +
    struct Test testStructVar;
 +
    struct Test * structPointer = &testStructVar;
 
     return 0;
 
     return 0;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Dați click pe numărul liniei 5 (în dreptul definiției variabilei <code>value</code>) și ar trebui ca în loc de numărul liniei să vă apară un pătrat roșu. Acela semnalează un ''breakpoint'' la acea linie. Dați apoi click pe '''Debug Project''' sau tastați <code>F11</code>. Acesta va programul în modul de ''debug'' și astfel se va opri la primul ''breakpoint'':
+
Având un pointer la o structură, accesul la câmpuri se poate face în două moduri:
 +
<ol>
 +
<li>Dereferențierea pointerului pentru a obține "valoarea" structurii și apoi utilizarea operatorului "." pentru accesul la membri:
 +
<syntaxhighlight lang="c" highlight="12">
 +
struct Test {
 +
    int intField;
 +
    float floatField;
 +
    char charField;
 +
    char charArrayField[100];
 +
};
 +
 
 +
int main() {
 +
    struct Test testStructVar;
 +
    struct Test * structPointer = &testStructVar;
 +
 
 +
    *structPointer.intField = 10;
 +
    return 0;
 +
}
 +
</syntaxhighlight>
 +
</li>
 +
<li>Accesul la membrii unei structuri accesate printr-un pointer este atât de popular și des întâlnit în C încât există un operator special care permite accesul la câmpuri fără dereferențiere. Acest operator este săgeata "->":
 +
<syntaxhighlight lang="c" highlight="13,14">
 +
struct Test {
 +
    int intField;
 +
    float floatField;
 +
    char charField;
 +
    char charArrayField[100];
 +
};
 +
 
 +
int main() {
 +
    struct Test testStructVar;
 +
    struct Test * structPointer = &testStructVar;
 +
 
 +
    *structPointer.intField = 10;
 +
    structPointer->floatField = 4.3;
 +
    structPointer->charField = 'B';
 +
    return 0;
 +
}
 +
</syntaxhighlight>
 +
</li>
 +
</ol>
  
[[Fișier:netbeans_debug.png|1000px|După execuție]]
+
Alocarea memoriei în HEAP pentru structuri se face identic cu alocarea pentru tipurile primitive de date:
 +
<syntaxhighlight lang="c" highlight="10, 14">
 +
#include <stdlib.h>
 +
struct Test {
 +
    int intField;
 +
    float floatField;
 +
    char charField;
 +
    char charArrayField[100];
 +
};
  
În interfață au apărut două zone noi:
+
int main() {
# Zona <span style="color: red">roșie</span> - instrumente de control al execuției în modul de debug; în ordine, acestea sunt:
+
    struct Test * structPointer = (struct Test*) malloc(sizeof(struct Test));
#* '''Finish Debugger Session''' - execuția se încheie și interfața grafică revine la forma anterioară;
 
#* '''Restart''' - execuția se reia de la începutul programului, oprindu-se la primul ''breakpoint'';
 
#* '''Pause''' - dacă programul nu este oprit (într-un ''breakpoint''), atunci accesta poate fi întrerupt facând click pe acest buton;
 
#* '''Continue''' - dacă programul este oprit (într-un ''breakpoint''), atunci el poate fi rulat mai reparte până la următorul ''breakpoint'' cu ajutorul acestui buton;
 
#* '''Step Over''' - programul va executa linia curentă (inclusiv orice apel de funcție ar fi pe linia curentă) și trece la linia următoare unde se oprește;
 
#* '''Step Into''' - programul va executa linia curentă iar dacă pe linia curentă există un apel de funcție, se va opri pe prima linie din funcția respectivă;
 
#* '''Step Out''' - programul va termina de executat funcția curentă și se va opri pe prima linie după linia unde a fost apelată funcția respectivă;
 
#* '''Run to Cursor''' - programul va executa toate liniile și se va opri pe linia unde este plasat cursorul; dacă acesta nu este plasat în calea execuției programului, acesta se va rula până la final;
 
#* '''Step Into Last or Selected Function Called From Current Line''' - similar cu '''Step Into''' doar că dacă există mai multe apeluri de funcții pe linia curentă, această opțiune permite selectarea celei în care se va opri programul.
 
  
# Zona <span style="color: blue">albastră</span> - vizualizarea stării execuției programului; aveți la dispoziție trei taburi noi:
+
    // use structPointer
#* '''Variables''' - aici se pot vedea valorile curente ale tuturor variabilelor valide în ''scope''-ul curent al programului; inclusiv aici se pot adăuga ''watches'' care permit vizualizarea valorilor calculate din expresii complexe scrise în limbajul C;
 
#* '''Call Stack''' - aici se poate vedea în orice moment stiva de execuție a programului;
 
#* '''Breakpoints''' - aici se poate vedea lista de ''breakpoints'' din programul curent.
 
  
Pentru a exersa utilizarea sistemului de debug, realizați următoarele operații:
+
    free(structPointer);
# Parcurgeti tot programul folosind '''Step Over''' și vizualizând valoarea variabilei <code>value</code> la fiecare pas, până la ultima linie din program, unde utilizați '''Continue'''.
+
    return 0;
# Reporniți aplicația în modul de debug și puneți un al doilea ''breakpoint'' pe linia cu apelul funcției <code>printf</code>.
+
}
# Folosiți '''Continue''' pentru a ajunge la al doilea ''breakpoint''.
+
</syntaxhighlight>
# Odată ajunși acolo, folosiți '''Step In''' pentru a intra în funcția <code>printf</code> (neavând codul sursă, veți vedea codul de asamblare obținut din dezasamblarea fișierului obiect de către GDB).
 
# Folosiți '''Step Out''' pentru a reveni în funcția <code>main</code> și apoi '''Continue''' pentru a termina execuția.
 
# Reporniți aplicația în modul de debug și realizați un ''watch'' cu valoarea lui <code>value + 1</code>. Vizualizați pas cu pas execuția programului.
 
  
== Tips & Tricks ==
+
== Adrese ca argumente de funcție ==
  
=== Suport pentru instrumente de ''Revision Control'' ===
+
Știm deja că în C argumentele funcțiilor sunt pass-by-value, asta înseamnă că funcției i se transmite valoarea dintr-o variabilă, nu variabila în sine. Astfel, dacă o variabilă este folosită ca argument la apelul unei funcții, modificarea argumentului în funcție nu se propagă și spre variabila sursă:
  
Netbeans oferă suport grafic pentru cele mai utilizate intrumente de ''revision control'': SVN, Git, Mercurial, etc. Pentru a crea un repository nou de Git pentru proiectul curent din interfața grafică, puteți da click dreapta pe numele proiectului, apoi '''Versioning -> Initialize Git repository...''':
+
<syntaxhighlight lang="c">
 +
#include <stdio.h>
  
[[Fișier:netbeans_git_init.png|1000px|Git Init]]
+
void divideByTwo(int arg) {
 +
    printf("Argument before division = %d\n", arg);
 +
    arg /= 2;
 +
    printf("Argument after division = %d\n", arg);
 +
}
  
După operațiunea de init, meniul '''Versioning''' se va schimba în '''Git''', de unde se pot realiza toate operațiile cunoscute din Git: add, remove, commit, push, pull, merge, rebase, diff, etc.
+
int main() {
 +
    int var = 10;
 +
    divideByTwo(var);
 +
    printf("Variabile after function call = %d\n", var);
 +
    return 0;
 +
}
 +
</syntaxhighlight>
  
=== Formatare automată a codului ===
+
Există o soluție pentru a permite propagarea modificării spre variabila sursă, și anume în loc de a trimite ca argument funcției variabila, se transmite adresa variabilei. Asta permite funcției să scrie direct în memoria alocată pentru variabilă și deci să-i modifice valoarea:
 +
<syntaxhighlight lang="c">
 +
#include <stdio.h>
  
Netbeans oferă opțiunea de a formata automat codul prin selectarea '''Source -> Format'''.
+
void divideByTwo(int *arg) {
 +
    printf("Argument before division = %d\n", *arg);
 +
    *arg /= 2;
 +
    printf("Argument after division = %d\n", *arg);
 +
}
  
Ce probleme rezolvă această formatare automată:
+
int main() {
* alinierea liniilor în funcție de blocurile de instrucțiuni și tipurile de ''statements'' de pe fiecare linie;
+
    int var = 10;
* plasarea sau eliminarea de spații acolo unde este necasar.
+
    divideByTwo(&var);
Ce probleme NU rezolvă această formatare automată;
+
    printf("Variabile after function call = %d\n", var);
* lipsa acoladelor de la blocurile '''if''' și '''for''';
+
    return 0;
* numele insuficient de sugestive pentru numele de funcții, structuri sau variabile.
+
}
 +
</syntaxhighlight>
  
Nu uitați de regulile următoare: [[Convenții de cod - C]]
 
  
<!--
 
 
= Exerciții =
 
= Exerciții =
  
*Săptămâna 1
+
<ol>
*# Realizați un proiect nou în Netbeans și scrieți un program care citească de la tastatură un număr întreg fără semn <code>size</code>. Se va aloca apoi dinamic în HEAP un vector de <code>size</code> valori numerice întregi pe 16 biți. Scrieți o funcție care să citească <code>size</code> valori de la tastatură și să le plaseze în vector. Scrieți apoi o funcție care să găsească valoarea maximă din vector. Apelați în <code>main</code> funcțiile de mai sus și afișați valoarea obținută pe ecran. Nu uitați să dezalocați memoria alocată. Pentru funcțiile de alocare și dezalocare de memorie, puteți recapitula [[PC Laborator 12]].
+
<li>Definiți o structură numită '''Dog''' ce trebuie conțină următoarele informații:
*# Modificați programul de mai sus astfel încât citirea variabilei <code>size</code> cât și alocarea de memorie să fie făcută într-o funcție separată. Această funcție trebuie să întoarcă un pointer cu adresa de început a vectorului dar și dimensiunea acestuia.
+
<ul>
*# Realizați un alt proiect în Netbeans și scrieți un program care citească de la tastatură un șir de caractere de lungime maximă 255. Folosind aritmetica pointerilor și o singură buclă '''for''', afișați pe ecran doar cifrele din șirul citit.
+
<li> nume </li>
* Săptămâna 2
+
<li> vârstă </li>
*# Realizați un proiect nou în Netbeans și scrieți un program care să citească de la tastatură un număr întreg fără semn <code>size</code>. Scrieți apoi o funcție care să aloce memorie pentru un șir de caractere care să conțină de <code>size</code> ori secvența "ha" (spre exemplu, pentru size == 3, string-ul va trebui să conțină "hahaha"), să umple șirul cu numărul cerut de "ha"-uri și să întoarcă adresa memoriei alocate. În funcția <code>main</code> afișați șirul de caractere (nu uitați la alocare de terminatorul de sfârșit de șir). Nu uitați să dezalocați memoria. Pentru funcțiile de alocare și dezalocare de memorie, puteți recapitula [[PC Laborator 12]].
+
<li> culoare </li>
*# Modificați programul de mai sus astfel încât în loc de secvența "ha", secvența repetată să fie citită de la tastatură cu <code>scanf</code> și trimisă ca argument funcției de alocare.  
+
<li> rasă </li>
*# Realizați un alt proiect în Netbeans și scrieți un program care să citească de la tastatură un șir de caractere de lungime maximă 255. Scrieți o singură funcție care să numere și să întoarcă numărul de litere și numărul de cifre din șirul citit. Aceste valori se vor afișa pe ecran în funcția <code>main</code>.
+
</ul>
-->
+
Realizați apoi o funcție care să citească date de la tastatură și să întoarcă o variabilă de tip <code>struct Dog</code> și o altă funcție care să ia ca argument un <code>struct Dog</code> și să afișeze informațiile pe ecran. Rasa se va citi ca o structură mystring. Nu uitați că după un apel de '''scanf''', în stream-ul standard de intrare va rămâne întotdeauna un caracter newline ('\n'). Dacă după un apel de '''scanf''' doriți citiți un șir de caractere cu '''fgets''', va trebui ca înainte de acest apel să apelați o dată '''getchar()''' care va citi și elimina din stream caracterul ('\n').
 +
</li>
 +
<li>Definiți o variabilă globală, de tip vector de <code>struct Dog</code> numită '''gCrazyDogLady''', de 100 câini. În funcția '''main''', citiți apoi un număr de câini '''n''' și apoi citiți informații legate de respectivii câini, folosind funcția de mai sus, și apoi stocând informațiile în vector. Dacă jumătate dintre câini au culoarea "black" si jumătate "white", atunci se va afișa "The crazy dog lady is a Cruella wannabe!"</li>
 +
 
 +
<li> În funcția '''main''' apoi, creați un pointer de tip corespunzator spre începutul vectorului '''gCrazyDogLady''', numit '''pTowardsADog'''. Iterați prin acest vector, folosind aritmetica pointerilor pe variabila '''pTowardsADog''' (incrementând-o de n ori) : </li>
 +
<ul>
 +
<li> pentru jumătate dintre câini, dereferențiați acel pointer și afișați informațiile câinilor - câte un câine pe rând (nume, varstă, culoare, rasă) </li>
 +
<li> pentru jumătate dintre câini, fără să dereferențiați acel pointer și afișați informațiile câinilor - câte un câine pe rând (nume, varstă, culoare, rasă) </li>
 +
</ul>
 +
<li> Folosind operatorul '''sizeof''' afisați numarul de bytes (și in paranteză rotundă, numărul de biți) pentru fiecare dintre tipurile de date: char, int, long, float, struct Dog. Ce observați? </li>
 +
<li> Folosind operatorul '''sizeof''' afisați numarul de bytes (și in paranteză rotundă, numărul de biți) pentru fiecare dintre tipurile de date: char*, int*, long*, float*, struct Dog*, void*. Ce observați? </li>
 +
</ol>

Versiunea curentă din 20 februarie 2026 09:17

Obiective : recapitulare structuri si pointeri

La sfârșitul acestei recapitulări studenții vor fi capabili:

  • să definească tipuri de date de tip struct;
  • să declare și să utilizele variabile de tip struct în programe;
  • să definească și să utilizeze tipuri de date pointer;
  • să folosească pointeri pentru a putea modifica variabilele trimise ca argumente unor funcții;
  • să aloce, să folosească și să elibereze memorie HEAP, în mod dinamic (in C si C++);
  • să utilizeze aritmetica pointerilor pentru a itera peste elemente de la adrese consecutive de memorie;

Structura în C

Tipurile de date struct sunt utilizate pentru a agrega mai multe multe varibile care au sens împreună. De exemplu, dorim să stocăm informații despre o mașină, prin urmare avem nevoie să stocăm marca, modelul, anul de fabricație, numărul de înmatriculare, culoarea, etc. Putem în acest caz să definim o structură numită Masina care să stocheze aceste valori. Variabilele care aparțin unei structuri se numesc câmpuri ale structurii. Un exemplu:

struct Masina {
    char marca[100];
    char model[50];
    unsigned short anFabricatie;
    char numarInmatriculare[8];
    char culoare[10];
};

Câmpurile structurii Masina sunt: marca, model, anFabricatie, numarInmatriculare și culoare.

Atenție: Definiția unei structuri nu implică automat și existența unei variabile de tipul respectiv, așa cum definirea tipului de date int nu implică existența unei varibile de tip int.

Declararea unei variabile de tipul Masina se face exact ca declararea oricărei alte variabile, sub forma: <tip_data> <nume_variabila>, cu observația că tipul de dată va conține și cuvântul cheie struct, deci acesta va fi struct Masina:

struct Masina {
    char marca[100];
    char model[50];
    unsigned short anFabricatie;
    char numarInmatriculare[8];
    char culoare[10];
};

int main() {
    struct Masina masina;
    return 0;
}

Odată definită o variabilă de tip struct, operatorul folosit pentru a accesa câmpurile structurii este .:

#include <stdio.h>

struct Masina {
    char marca[100];
    char model[50];
    unsigned short anFabricatie;
    char numarInmatriculare[8];
    char culoare[10];
};

int main() {
    struct Masina masina_mea;
    printf("Care este marca masinii? ");
    fgets(masina_mea.marca, 100, stdin);
    printf("Care este modelul masinii? ");
    fgets(masina_mea.model, 50, stdin);
    printf("Care este numarul de inmatriculare al masinii? ");
    fgets(masina_mea.numarInmatriculare, 8, stdin);
    printf("Care este culoarea masinii? ");
    fgets(masina_mea.culoare, 10, stdin);
    printf("Care este anul de fabricatie a masinii? ");
    scanf("%hu", &masina_mea.anFabricatie);

    return 0;
}
 Observație: O structură poate avea câmpuri de orice tip, inclusiv de tipul altor structuri.

Un tip de dată de tip structură poate fi folosit ca orice alt tip de dată, spre exemplu pentru a crea vectori de acel tip, dar și pentru a defini funcții care au argumente sau întorc valori de acel tip:

#include <stdio.h>

struct Masina {
    char marca[100];
    char model[50];
    unsigned short anFabricatie;
    char numarInmatriculare[8];
    char culoare[10];
};

struct Masina citesteMasina() {
    struct Masina masina_mea;
    printf("Care este marca masinii? ");
    fgets(masina_mea.marca, 100, stdin);
    printf("Care este modelul masinii? ");
    fgets(masina_mea.model, 50, stdin);
    printf("Care este numarul de inmatriculare al masinii? ");
    fgets(masina_mea.numarInmatriculare, 8, stdin);
    printf("Care este culoarea masinii? ");
    fgets(masina_mea.culoare, 10, stdin);
    printf("Care este anul de fabricatie a masinii? ");
    scanf("%hu", &masina_mea.anFabricatie);
    return masina_mea;
}

void afiseazaMasina(struct Masina masina){
    printf("Masina marca %s si modelul %s are numarul de inmatriculare "
            "%s, culoarea %s si a fost fabricata in anul %hu!\n",
            masina.marca, masina.model, masina.numarInmatriculare,
            masina.culoare, masina.anFabricatie);
}

int main() {
    struct Masina parcAuto[10];
    parcAuto[0] = citesteMasina();

    afiseazaMasina(parcAuto[0]);

    return 0;
}


Plecând de la limbajul C, ne amintim că acesta pune la dispoziție o serie de tipuri de date primitive, incluse în standardul limbajului. Ca exemple, avem: int, long, char, double, etc. În plus, există posibilitatea de a crea structuri compuse folosind cuvântul cheie struct. O structură în C este compusă din una sau mai multe variabile care pot fi ori de tip primitiv, ori alte structuri, ori o combinație din cele două.


În continuare avem un exemplu de definiție a unor structuri în C.

struct mystring{
    char* str;
    unsigned length;
};

struct person{
    struct mystring *first_name;
    struct mystring *last_name;
    unsigned age;
    float height;
    float weight;
};

Se vede că structura person conține pointeri la două structuri de tip mystring. Relația este descrisă de schema bloc următoare (cu exemple de valori pentru variabilele primitive):

     struct person
-------------------------                                          char char ...                      
| struct mystring*    ● |------>       struct mystring             --------------------------------------
-------------------------          -------------------------       | G | h | e | o | r | g | h | e | \0 |
| struct mystring*    ● |---       | char*               ● | ----> --------------------------------------
-------------------------  |       -------------------------
| unsigned           29 |  |       | unsigned           10 |
-------------------------  |       -------------------------
| float             1.7 |  |
-------------------------  |                                       char char ...
| float            68.9 |  ---->      struct mystring              ------------------------------
-------------------------          -------------------------       | V | a | s | i | l | e | \0 |
                                   | char*               ● |---->  ------------------------------
                                   -------------------------
                                   | unsigned           10 |
                                   -------------------------

În continuare, vom da un exemplu de utilizare a acestor structuri:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

int main(){
    //make a default mystring with no content and length = 30
    struct mystring * some_string = (struct mystring*)malloc(sizeof(struct mystring));
    unsigned default_length = 30;
    some_string->length = default_length;
    some_string->str = (char*)malloc(default_length * sizeof(char));
    strcpy(some_string->str, "Vasile");

    //make a person structure in which all strings refer to the default empty mystring
    struct person* new_person = (struct person*)malloc(sizeof(struct person));
    new_person->first_name = some_string;
    new_person->last_name = some_string;
    new_person->age = 29;
    new_person->height = (float)1.7;
    new_person->weight = 68.9F;

    printf("Persoana se numeste %s %s, are varsta de %d ani, inaltimea %f si greutatea %f\n", new_person->first_name->str, 
        new_person->last_name->str,
        new_person->age,
        new_person->height,
        new_person->weight);

    return 0;
}

În acest exemplu, schema bloc este diferită dintr-un punct de vedere esențial: ambii pointeri de tip mystring sunt referință la aceeași adresă, respectiv la același mystring. Astfel, dacă se modifică new_person->first_name, atunci implicit se modifică și new_person->last_name (de fapt este aceeași structură):

-------------------------
| struct person         |
-------------------------         -------------------------       char char...             char ...
| struct mystring*    ● |------>  | struct mystring       |       ------------------------------------------------
-------------------------    /    -------------------------       | V | a | s | i | l | e | \0 |    |  ...   |   |
| struct mystring*    ● |---/     | char*               ● |-----> -----------------------------------------------
-------------------------         -------------------------
| unsigned           29 |         | unsigned           30 |
-------------------------         -------------------------
| float             1.7 |  
-------------------------  
| float            68.9 |    
-------------------------

Pentru a face un rezumat, structura, in C, este un tip de dată compusă din tipuri primitive, sau alte structuri. Analog cu orice alt tip de dată, se pot defini variabile de tipul structurii, așa cum se pot defini variabile de tip primitiv. Limitarea fundamentală a structurilor este că acestea nu pot conține decât date, nu și funcții. În laboratorul următor vom vedea aceasta limitare rezolvată prin noțiunea de clasă.

Tipuri de date pointer

Un pointer reprezintă o variabilă care stochează o adresă în memoria dedicată aplicației. Tipul variabilei de tip pointer specifică tipul datei care poate fi citit de la adresa respectivă.

O variabilă de tip pointer se definește în felul următor:

<tip_data> * <nume_variabila>;

Spre exemplu:

int * pa;

Variabila de tip pointer pa nu memorează un întreg, ci o adresă în memorie, iar de la adresa respectivă se poate citi un întreg. Acest lucru se numește indirectare simplă. În plus, deoarece tipul pointer este în sine un tip de dată, și în definiția unui pointer <tip_data> poate fi un pointer, aceasta permite următoarele construcții:

  • int * pa; - variabilă ce stochează o adresă de unde se poate citi un întreg (indirectare simplă);
  • int ** pa; - variabilă ce stochează o adresă de unde se poate citi o adresă de unde se poate citi un întreg (dublă indirectare);
  • int *** pa; - variabilă ce stochează o adresă de unde se poate citi o adresă de unde se poate citi o adresă de unde se poate citi un întreg (triplă indirectare);
  • etc.
Regulă: Orice variabilă este stocată în memorie și deci are o adresă în memorie.

În C există un tip de dată pointer care poate memora o adresă fără a ști ce date se află la adresa respectivă. Acest tip de pointer este void*.

Dimensiunea tipului de date pointer

Conform regulii de mai sus, și variabilele de tip pointer ocupă loc în memorie, deci au dimensiune, în octeți. Un pointer nu oferă informații legate de dimensiunea ocupată de datele de la adresa respectivă, ci doar adresa de unde începe zona ocupată. Din moment ce un pointer memorează doar o adresă, dimensiunea variabilelor de tip pointer nu depinde decât de dimensiunea spațiului de memorie. Astfel, pentru procesoare și sisteme de operare pe 32 de biți, o variabilă de tip pointer va avea 32 de biți, iar pe procesoare și sisteme de operare pe 64 de biți, o variabilă de tip pointer va avea 64 de biți.

Adresa unei variabile

Operatorul care permite aflarea adresei unde este stocată o variabilă este ampersand (&). Acesta este un operator unar ce se plasează înaintea unei variabile iar rezultatul evaluării sale este adresa unde este stocată variabila respectivă. Această adresă poate fi stocată într-o altă variabilă de tipul corespunzător. Altfel spus, pentru o variabilă de tip tip_data, adresa acesteia se poate stoca într-o variabilă de tip tip_data *:

float floatValue;
float * floatAddress = &floatValue;

int intValue;
int * intAddress = &intValue;
int ** intPointerAddress = &intAddress;

Adresa NULL

Pentru orice aplicație, adresa 0 din spațiul ei de memorie este rezervată. Aceasta nu poate fi nici scrisă și nici citită. Această adresa poartă numele de NULL. Orice variabilă de tip pointer poate lua valoarea NULL, lucru care de obicei specifică faptul că de fapt variabila pointer nu stochează o adresă validă.

Constanta NULL este definită în fișierul header stdlib.h (Standard Library).
#include <stdlib.h>

int main() {
    char * charPointer = NULL;
    return 0;
}

Valoarea de la o adresă

Având o variabilă de tip pointer, valoarea stocată în memorie la adresa respectivă se poate afla folosind caracterul steluță (*), numit și operator de indirectare. Acesta este un operator unar ce se plasează înaintea unei variabile de tip pointer iar rezultatul evaluării sale este valoarea din memorie de la adresa stocată în variabila respectivă. Această valoare poate fi stocată într-o altă variabilă de tipul corespunzător. Altfel spus, pentru o variabilă pointer de tip tip_data *, valoarea de la adresa stocată de pointerul respectiv se poate memora într-o altă variabilă de tip tip_data:

float floatValue;
float * floatAddress = &floatValue;
float anotherFloatValue = *floatAddress;

int intValue;
int * intAddress = &intValue;
int ** intPointerAddress = &intAddress;
int * anotherintAddress = *intPointerAddress;


Atenție: Accesarea valorii de la o adresă se numește dereferențiere. Dereferențierea unei adrese care nu face parte din spațiul de memorie al aplicației sau dereferențierea lui NULL va avea ca efect oprirea imediată a programului printr-un Segmentation fault.
Atenție: Nu confundați steluța utilizată la definirea unui pointer cu operatorul de indirectare/ dereferențiere. Aceștia sunt la fel de diferiți cum este și operatorul de inmulțire față de cel de indirectare.

Utilitatea variabilelor de tip pointer

Pointerii în C au două roluri foarte importante:

  1. Alocarea dinamică de memorie în HEAP
  2. Modificarea variabilelor parametri ale unor funcții astfel încât modificarea să se păstreze în afara funcției

Alocarea dinamică de memorie

Memoria alocată unei aplicații de către sistemul de operare este împărțită în mai multe secțiuni, dintre care importante pentru stocarea de date sunt:

  • Segmentele BSS și Data - reprezintă memoria alocată pentru variabilele statice (globale), care există de la începutul până la încheierea programului, fără posibilitate de eliberare;
  • Stiva (Stack) - zona de memorie în care se alocă contextele funcțiilor apelate în timpul execuției programului și în care se alocă argumentele și variabilele locale are funcțiilor; această zonă este alocată la intrarea în funcție și este eliberată la ieșirea din funcție;
  • HEAP - zonă de memorie în care se pot aloca dinamic, de către programator, blocuri de memorie ce pot fi folosite în program până la eliberarea acestora de către programator.

Alocarea și dezalocarea memoriei în heap se fac folosind următoarele funcții:

  • void * malloc(unsigned size) - alocă size octeți într-o zonă continuă din HEAP și întoarce adresa de memorie unde începe zona respectivă; dacă alocarea eșuează (nu exită suficientă memorie într-o zonă continuă în HEAP), funcția va întoarce NULL; alocarea nu șterge conținutul memoriei respective;
  • void * calloc(unsigned elements, unsigned elementSize) - alocă elements elemente de elementSize octeți fiecare într-o zonă continuă din HEAP și întoarce adresa de memorie unde începe zona respectivă; dacă alocarea eșuează (nu exită suficientă memorie într-o zonă continuă în HEAP), funcția va întoarce NULL; alocarea șterge tot conținutul memoriei respective, scriind 0 la fiecare locație;
  • void free (void *) - dezalocă o zonă de memorie alocată în prealabil cu malloc sau calloc; apelul succesiv de două sau mai multe ori a funcției free pentru aceeași zonă de memorie sau apelul ei pentru o adresă care nu a fost alocată în prealabil va avea ca efect oprirea imediată a programului cu eroare (double free or corruption).

Toate aceste funcții sunt definite în fișierul header stdlib.h.

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

int main(){
    int * intPointer;
    short * shortPointer;

    intPointer = (int*) malloc(sizeof(int));
    shortPointer = (short*) calloc(1, sizeof(short));

    printf("Valoarea din zona alocata pentru int este: %d\n", *intPointer);
    printf("Valoarea din zona alocata pentru short este: %hd\n", *shortPointer);

    free(intPointer);
    free(shortPointer);

    return 0;
}
Atenție: Deoarece funcțiile malloc și calloc au doar rolul de a aloca memorie, fără să știe care este scopul utilizării acestei memorii, ele întorc un pointer de tip void *. Astfel, pentru a putea stoca adresa într-un alt tip de pointer (de exemplu int *), ea trebuie convertită la tipul de date corect. Aceasta este explicația prezenței operatorului de cast din fața apelului funcțiilor malloc și calloc din codul de mai sus.

Aritmetica pointerilor

Când se alocă memorie in HEAP, rareori se alocă pentru un singur element, de cele mai multe ori se alocă pentru un număr mare de elemente de același fel. Ca exemplu, dacă vrem să stocăm o imagine High Definition, ne trebuie o zonă de memorie care să poată memora informație de culoare pentru 1920 * 1080 de pixeli, fiecare pixel având informație de culoare pentru roșu, verde și albastru (RGB). Fiecare din aceste componente de culoare se stochează pe un octet ca valoare întreagă fără semn (unsigned char). Prin urmare, pentru un frame se vor aloca 1920 * 1080 * 3 octeți = 6220800, aproape 6 MB. Această memorie se alocă întotdeauna într-o zonă continuă de către funcțiile malloc și calloc:

#include <stdlib.h>
int main(){
    unsigned char * frame;
    frame = (unsigned char*) malloc(1920 * 1080 * 3 * sizeof(unsigned char));
    //... use frame

    free (frame);
    return 0;
}

Deoarece pointer-ul nu memorează decât adresa de început a zonei de memorie, există posibilitatea de a modifica adresa pentru a accesa elementele ulterioare. În acest scop, variabilele de tip pointer suportă doar operații aritmetice de adunare sau scădere, nu și de înmulțire sau împărțire.

Atenție: Incrementarea unui pointer nu crește adresa cu 1, ci cu dimensiunea tipului de dată al pointer-ului. Deci pentru un int * p;, linia p++; va incrementa adresa cu 4 (sizeof(int)):
#include <stdio.h>
#include <stdlib.h>

int main(){
    int * pointer;
    pointer = (int*) malloc(10 * sizeof(int));
    printf("The pointer address is: %p\n", pointer);
    printf("The pointer address + 1 is: %p\n", pointer + 1);
    free (pointer);
    return 0;
}
Atenție ca prin operații pe pointeri să nu depășiți zona de memorie alocată.

Pointerii și vectorii

Memoria alocată pentru mai multe elemente de același fel reprezintă de fapt un vector de elemente de acel tip. Astfel aflăm că de fapt vectorii și pointerii, în multe situații se pot folosi interschimbabil.

Când se definește un vector, numele vectorului reprezintă un pointer la adresa de unde începe zona lui de memorie, adică adresa unde este memorat elementul de la indexul 0:
#include <stdio.h>
#include <stdlib.h>

int main(){
    int array[10];
    array[0] = 13;
    printf("The array pointer address is: %p\n", array);
    printf("The value at address %p is %d\n", array, *array);
    return 0;
}
Dereferențierea unui pointer indexat cu o valoare este echivalentă cu folosirea operatorului de acces la vector: *(v + k) == v[k], unde v este un pointer (sau vector) iar k este un întreg.
#include <stdlib.h>
#include <stdio.h>
int main(){
    int arraySize;
    printf("size = ");
    scanf("%d", &arraySize);

    int * heapArray;
    heapArray = (int*) malloc(arraySize * sizeof(int));

    int i;
    for(i = 0; i < arraySize; i++) {
        printf("heapArray[%d] = ", i);
        scanf("%d", &heapArray[i]);
    }

    for(i = 0; i < arraySize; i++) {
        printf("heapArray[%d] = %d\n", i, *(heapArray + i));
    }


    free (heapArray);
    return 0;
}

Pointerii și structurile

Ca orice tip de dată, și variabilele de tip structură ocupă loc în memorie și deci pot exista pointeri de tip structură:

struct Test {
    int intField;
    float floatField;
    char charField;
    char charArrayField[100];
};

int main() {
    struct Test testStructVar;
    struct Test * structPointer = &testStructVar;
    return 0;
}

Având un pointer la o structură, accesul la câmpuri se poate face în două moduri:

  1. Dereferențierea pointerului pentru a obține "valoarea" structurii și apoi utilizarea operatorului "." pentru accesul la membri:
    struct Test {
        int intField;
        float floatField;
        char charField;
        char charArrayField[100];
    };
    
    int main() {
        struct Test testStructVar;
        struct Test * structPointer = &testStructVar;
    
        *structPointer.intField = 10;
        return 0;
    }
    
  2. Accesul la membrii unei structuri accesate printr-un pointer este atât de popular și des întâlnit în C încât există un operator special care permite accesul la câmpuri fără dereferențiere. Acest operator este săgeata "->":
    struct Test {
        int intField;
        float floatField;
        char charField;
        char charArrayField[100];
    };
    
    int main() {
        struct Test testStructVar;
        struct Test * structPointer = &testStructVar;
    
        *structPointer.intField = 10;
        structPointer->floatField = 4.3;
        structPointer->charField = 'B';
        return 0;
    }
    

Alocarea memoriei în HEAP pentru structuri se face identic cu alocarea pentru tipurile primitive de date:

#include <stdlib.h>
struct Test {
    int intField;
    float floatField;
    char charField;
    char charArrayField[100];
};

int main() {
    struct Test * structPointer = (struct Test*) malloc(sizeof(struct Test));

    // use structPointer

    free(structPointer);
    return 0;
}

Adrese ca argumente de funcție

Știm deja că în C argumentele funcțiilor sunt pass-by-value, asta înseamnă că funcției i se transmite valoarea dintr-o variabilă, nu variabila în sine. Astfel, dacă o variabilă este folosită ca argument la apelul unei funcții, modificarea argumentului în funcție nu se propagă și spre variabila sursă:

#include <stdio.h>

void divideByTwo(int arg) {
    printf("Argument before division = %d\n", arg);
    arg /= 2;
    printf("Argument after division = %d\n", arg);
}

int main() {
    int var = 10;
    divideByTwo(var);
    printf("Variabile after function call = %d\n", var);
    return 0;
}

Există o soluție pentru a permite propagarea modificării spre variabila sursă, și anume în loc de a trimite ca argument funcției variabila, se transmite adresa variabilei. Asta permite funcției să scrie direct în memoria alocată pentru variabilă și deci să-i modifice valoarea:

#include <stdio.h>

void divideByTwo(int *arg) {
    printf("Argument before division = %d\n", *arg);
    *arg /= 2;
    printf("Argument after division = %d\n", *arg);
}

int main() {
    int var = 10;
    divideByTwo(&var);
    printf("Variabile after function call = %d\n", var);
    return 0;
}


Exerciții

  1. Definiți o structură numită Dog ce trebuie să conțină următoarele informații:
    • nume
    • vârstă
    • culoare
    • rasă

    Realizați apoi o funcție care să citească date de la tastatură și să întoarcă o variabilă de tip struct Dog și o altă funcție care să ia ca argument un struct Dog și să afișeze informațiile pe ecran. Rasa se va citi ca o structură mystring. Nu uitați că după un apel de scanf, în stream-ul standard de intrare va rămâne întotdeauna un caracter newline ('\n'). Dacă după un apel de scanf doriți să citiți un șir de caractere cu fgets, va trebui ca înainte de acest apel să apelați o dată getchar() care va citi și elimina din stream caracterul ('\n').

  2. Definiți o variabilă globală, de tip vector de struct Dog numită gCrazyDogLady, de 100 câini. În funcția main, citiți apoi un număr de câini n și apoi citiți informații legate de respectivii câini, folosind funcția de mai sus, și apoi stocând informațiile în vector. Dacă jumătate dintre câini au culoarea "black" si jumătate "white", atunci se va afișa "The crazy dog lady is a Cruella wannabe!"
  3. În funcția main apoi, creați un pointer de tip corespunzator spre începutul vectorului gCrazyDogLady, numit pTowardsADog. Iterați prin acest vector, folosind aritmetica pointerilor pe variabila pTowardsADog (incrementând-o de n ori) :
    • pentru jumătate dintre câini, dereferențiați acel pointer și afișați informațiile câinilor - câte un câine pe rând (nume, varstă, culoare, rasă)
    • pentru jumătate dintre câini, fără să dereferențiați acel pointer și afișați informațiile câinilor - câte un câine pe rând (nume, varstă, culoare, rasă)
  4. Folosind operatorul sizeof afisați numarul de bytes (și in paranteză rotundă, numărul de biți) pentru fiecare dintre tipurile de date: char, int, long, float, struct Dog. Ce observați?
  5. Folosind operatorul sizeof afisați numarul de bytes (și in paranteză rotundă, numărul de biți) pentru fiecare dintre tipurile de date: char*, int*, long*, float*, struct Dog*, void*. Ce observați?