This is an old revision of the document!


Laborator 1 - Recapitulare PC. Vectori si matrice. Alocare dinamica

Responsabili

În cadrul acestui laborator ne propunem să recapitulam cateva dintre conceptele de C invatate la cursul de Programarea Calculatoarelor.

Obiective

Ne dorim să:

  • Recapitulam alocarea dinamica a memoriei si pointerii
  • Recapitulam lucrul cu structuri
  • Recapitulam directivele de preprocesare
  • Invatam sa ne asiguram ca nu avem memory leaks in programul nostru

Pass by value vs Pass by address

C este un limbaj pass-by-value. Asta inseamna ca functiile isi vor creea copii ale parametrilor si vor lucra cu ele. Daca vrem sa lucram direct pe variabilele trimise ca parametru, va trebui sa trimitem adresa lor catre functie.

main.c
#include <stdio.h>
 
// Se va face cate o copie a variabilelor a si b. Aceste copii se vor distruge
// dupa ce functia isi va incheia executia
void swap(int a, int b) {
     int temp = a;
     a = b;
     b = a;
}
 
// Se va face cate o copie a pointerilor dar vor pointa tot catre variabilele
// a si b
void swap2(int* a, int* b) {
     int temp = *a;
     *a = *b;
     *b = temp;
}
 
int main() {
     int a = 5;
     int b = 10;
 
     printf("Before swap: %d %d\n", a, b); // 5 10
     swap(a, b);
     printf("After swap: %d %d\n", a, b); // 5 10
 
 
     printf("Before swap: %d %d\n", a, b); // 5 10
     swap2(&a, &b);
     printf("After swap: %d %d\n", a, b); // 10 5
 
     return 0;
}

Void pointer (void*)

Un pointer de tipul void* este un pointer care nu este asociat cu niciun tip de date, ceea ce ii permite sa pointeze la adrese de memorie de orice tip. De asemenea, el poate fi castat la orice tip de date.

Exemplu de folosire in contextul genericitatii

main.c
#include <stdio.h>
 
enum types {
   INT,
   DOUBLE,
   STRING
};
 
void print(void* var, enum types type) {
   if (type == INT) {
       printf("%d\n", *((int*)var));
   }
   if (type == DOUBLE) {
       printf("%lf\n", *((double*)var));
   }
   if (type == STRING) {
       printf("%s\n", (char*)var);
   }
}
 
int main() {
   int a = 123;
   double b = 2.67;
   char* c = "wubba lubba dub dub";
 
   print(&a, INT);
   print(&b, DOUBLE);
   print(c, STRING);
 
   return 0;
}

Alocarea dinamica

Vectori

Printr-un vector se înţelege o colecţie liniară şi omogenă de date. Un vector este liniar pentru că datele(elementele) pot fi accesate în mod unic printr-un index. Un vector este, de asemenea, omogen, pentru că toate elementele sunt de acelaşi tip. În limbajul C, indexul este un număr întreg pozitiv şi indexarea se face începând cu 0.

Declarare vector static:

int a[100]; // structură statică: dimensiunea acestuia trebuie să fie o constantă la compilare şi nu poate fi modificată în cursul
            // execuţiei programului.

Declarare vector alocat dinamic:

int *a = malloc(n * sizeof(int));free(a);

În limbajul C nu există practic nici o diferenţă între utilizarea unui vector cu dimensiune fixă şi utilizarea unui vector alocat dinamic.

Matrice

Matricea este o colecţie omogenă şi bidimensională de elemente. Acestea pot fi accesate prin intermediul a doi indici, numerotaţi, ca şi în cazul vectorilor, începand de la 0.

Declarare matrice statica:

int mat[5][10];

Declarare matrice dinamica:

int **a;
...
a = malloc(nl * sizeof(int *));   // Alocare pentru vector de pointeri
for (i = 0; i < nl; ++i) {
   a[i] = malloc(nc * sizeof(int));  // Alocare pentru o linie
}
...
for (i = 0; i < nl; ++i) {
     free(a[i]);
}
free(a);

Daca se cunoaste la compilare prima dimensiune a matricei (numarul de linii), un alt mod de a declara o matrice dinamic este urmatorul:

int (*mat)[10] = (int (*)[10])malloc(sizeof(*mat) * 5);
...
free(mat);

In acest caz, toata matricea va fi alocata intr-o zona continua de memorie.

Pentru a aloca dinamic un vector putem folosi si functia calloc. Exista doua mari diferente intre calloc si malloc:

  • Semnatura functiei: calloc() primeşte două argumente, o lungime de vector şi o dimensiune a fiecărui element.
  • calloc initializeaza elementele cu 0, malloc nu face niciun fel de initializare.

Realocarea

Redimensionarea unui vector care creste (sau scade) fata de dimensiunea alocata initial se poate face cu funcţia realloc, care primeşte adresa veche şi noua dimensiune şi întoarce noua adresă. In cazul in care realocarea a esuat, la fel ca celelalte functii de alocare, realloc va intoarce NULL.

Static

Variabile statice

Variabilele statice reprezinta un tip special de variabile care isi pastreaza valoarea in program pana la terminarea executiei lui. Ele sunt stocate intr-o parte separata a memoriei, astfel incat dupa iesirea din scope sau stack frame ele nu se distrug.

#include <stdio.h>
void f() {
    static int i = 0;
    ++i;
    printf("%d ", i);
}
 
int main() {
    for (int i = 0; i < 3; ++i) {
        f();
    }
 
    return 0;
}

Functii statice

Declarerea unei functii statice ii va restrictiona acesteia accesul la fisierul in care este declarata. Astfel, daca fisierul sursa al acesteia este inclus in altul si iar functia este apelata din el, vom primi o eroare de compilare, functia statica nefiind inclusa.

f1.c
static void f() {
    printf(“imported”);
}
f2.c
#include “f1.c”;
 
int main() {
    f(); // Se va genera o eroare de tip “undefinied reference to f” deoarece functia statica nu a fost importata.
    return 0;
}

Const

Keyword-ul const desemneaza o variabila constanta, read-only, a carei valoare nu se va mai schimba dupa initializare. Daca se incearca schimbarea valorii se va genera o eroare de compilare. In cazul pointerilor fiti atenti daca adresa de memorie este constanta sau valorea spre care pointeaza!

Deducerea acestor declaratii se poate face prin clockwise/spiral rule.

Exemple:

int* a; // pointer de tip int
int* const a; // pointer constant catre un int variabil
int const *a; // pointer variabil catre un int constant
int const * const a; // pointer constant catre un int constant

Pentru syntactic sugar putem muta primul const la inceputul declaratiei:

const int* a == int const* a (veti vedea de multe ori acest tip de declaratie)

Directive de preprocesor

Definirea de macro-uri

#define MAX 50

Putem defini constante ce vor fi inlocuite peste tot in program in etapa de procesare. Este recomandat sa folosim aceasta optiune in defavoarea scrierii efective a constantei deoarece suntem mai predispusi la bug-uri putand uita sa modificam constanta in unele parti ale programului.

Garzi

Folosim garzi de preprocesare pentru a evita incluziunea multipla si redefinirea de variabile. Astfel, chiar daca includem de mai multe ori acelasi header, textual, vom avea o singura inlocuire a variabilelor/functiilor.

engine.h
struct A{};
car.h
car.h
#include “engine.h” // se va inlocui cu struct A la preprocesare
main.c
#include “engine.h”
#include “car.h”
 
...// Vom avea inclusa de doua ori struct A, ceea ce va duce la o eroare de compilare 
   // deoarece se incearca redefinirea lui A.

Solutie: Adaugam garzi in fiecare fisier

engine.h
#ifndef __ENGINE_H__
#define __ENGINE_H__
 
struct A {};
 
#endif
car.h
#ifndef __CAR_H__
#define __CAR_H__
 
#include "engine.h"
 
#endif
main.c
#include "engine.h"
#include "car.h"
 
int main() {
    return 0;
}

Dupa expandare vom avea:

main.c
#ifndef __ENGINE_H__ // Adaugam o intrare pentru "engine" in tabela de simboluri
#define __ENGINE_H__
 
struct A {};
 
#endif
 
#ifndef __CAR_H__ // Adaugam o intrare pentru "car" in tabela de simboluri
#define __CAR_H__
 
#ifndef __ENGINE_H__ // "engine" este definit deci sarim peste acest branch si nu includem struct A din nou
#define __ENGINE_H__
 
struct A {};
 
#endif
 
#endif
 
int main() {
    return 0;
}

Exerciții

Fiecare laborator va avea unul sau doua exerciții publice si un pool de subiecte ascunse, din care asistentul poate alege cum se formeaza celelalte puncte ale laboratorului.

[4p] Se citesc de la tastatura N cercuri definite prin coordonatele centrului si raza (toate, numere intregi). Sa se numere cate perechi de cercuri se intersecteaza.

Ex:
6
25 25 15
10 10 12
25 20 7
40 40 5
48 40 5
0 30 5

Output: 4 (se intersecteaza perechile de cercuri: (1, 2), (1, 3), (2, 3), (4, 5))

311CAa 2) [6p] Un robot pleacă de la coordonatele (0, 0) ale unei matrice de dimensiune n x m. El primește dimensiunile matricei si o serie de comenzi codificate astfel ‘U’ → up (merge o casuță în sus), ‘D’ → down (merge o casuță în jos), ‘R’ → right (merge o casuță la dreapta), ‘L’ → left (merge o casuță la stânga). Robotul primește comenzile ca un șir de caractere (ex: “DDURLLR”). Întoarceți true dacă robotul ajunge înapoi în bază după aceste comenzi (coordonatele (0, 0)) sau false în caz contrar. Afisati matricea dupa fiecare miscare, marcand pozitia robotului cu un caracter distinct.

  • [2p] Alocați dinamic matricea
  • [2p] Implementați logica problemei
  • [2p] Verificați cu Valgrind că nu aveți memory leaks

313CAb 2) [6p] Se citește o matrice de dimensiune n x m. Se poate efectua operația XOR de-a lungul unei linii sau a unei coloane. Să se afișeze rezultatul maxim.

  • [2p] Alocați dinamic matricea
  • [2p] Implementați logica problemei
  • [2p] Verificați cu Valgrind că nu aveți memory leaks

311CAb 2) [6p] Se citește o matrice de dimensiune n x m. Se primesc alte două dimensiuni n’ si m’. Se va face conversia de la matricea de n x m la matricea n’ x m’ și se va întoarce noua matrice (dacă este posibil). În caz contrar se va afișa vechea matrice.

  • [2p] Alocați dinamic matricea
  • [2p] Implementați logica problemei
  • [2p] Verificați cu Valgrind că nu aveți memory leaks

Ex:

    n = 2
    m = 2
    [[1, 2], [3, 4]]
  
    n’ = 1
    m’ = 4
    [1, 2, 3, 4]

314CAa 2) [6p] Se citește o matrice de dimensiune n x m. Fiecare linie va forma un număr. Exemplu: linia [3, 0, 2] va forma numărul 302. Afișați suma liniilor.

  • [2p] Alocați dinamic matricea
  • [2p] Implementați logica problemei
  • [2p] Verificați cu Valgrind că nu aveți memory leaks

313CAa 2) [6p] Se citește o matrice de dimensiune n x m cu toate elementele 0 și un șir de operații formate din perechi de câte două numere a și b. La fiecare operatie, se incrementeaza toate elementele matricei aflate in dreptunghiul cu coltul stanga-sus in [0,0] si dreapta-jos in [a,b].

  • [2p] Alocați dinamic matricea
  • [2p] Implementați logica problemei
  • [2p] Verificați cu Valgrind că nu aveți memory leaks

Exemplu:

m = 3 n = 3

Operații: [2, 2], [3, 3]

Inițial, M va fi

0 0 0

0 0 0

0 0 0

După [2, 2], M va fi

1 1 0

1 1 0

0 0 0

După [3, 3], M va fi

2 2 1

2 2 1

1 1 1

Afișați matricea finală.

315CAa 2) [6p] Se citește o matrice de dimensiune n x m. Elementul ei minim va fi 1. Verificați că matricea conține doar numere consecutive (suma tuturor elementelor == n * (n + 1) / 2)

  • [2p] Alocați dinamic matricea
  • [2p] Implementați logica problemei
  • [2p] Verificați cu Valgrind că nu aveți memory leaks

315CAb 2) [6] Se citește o matrice de dimensiune n x m sortată crescător pe linii. Elementele ei vor fi doar 0 și 1. Să se afișeze index-ul liniei cu cei mai multi de 1.

  • [2p] Alocați dinamic matricea
  • [2p] Implementați logica problemei
  • [2p] Verificați cu Valgrind că nu aveți memory leaks

Interviu

Această secțiune nu este punctată și încearcă să vă facă o oarecare idee a tipurilor de întrebări pe care le puteți întâlni la un job interview (internship, part-time, full-time, etc.) din materia prezentată în cadrul laboratorului.

  • Care este diferenta dintre pass by value si pass by address?
  • Ce intoarce functia malloc? De ce?
  • Care sunt diferentele dintre alocarea dinamica si alocarea statica?
  • Ce se intampla daca un header este inclus de doua ori?

Și multe altele…

Bibliografie obligatorie

Bibliografie recomandată

sd-ca/laboratoare/lab-01.1582791085.txt.gz · Last modified: 2020/02/27 10:11 by george.popescu0309
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0