Resposabili:
Limbajele de programare ne permit să-i transmitem unui calculator instrucțiuni pe care acesta să le înțeleagă și să le execute. Deși fiecare limbaj de programare are particularitățile lui, ele au în comun multe concepte similare, care, odată înțelese într-o manieră generică, pot fi aplicate în aproape orice limbaj de programare.
Exemple de astfel de concepte includ:
if
, while
, for
, return
);Pentru a înțelege cum se aplică astfel de concepte într-un limbaj de programare, trebuie să învățăm să căutăm și să aplicăm corect noțiunile prezentate în documentația acelui limbaj.
În programare, documentația este o colecție de texte, imagini, videoclipuri care au ca scop descrierea funcționalității unui software.
În particular, documentația unui limbaj de programare descrie funcționalitățile oferite de acel limbaj, sub forma unor reguli de sintaxă, dar și a unor biblioteci (standard sau non-stardard), framework-uri etc.
Documentațiile pot fi scrise atât de dezvoltatorii limbajelor, cât și de alte organizații, de aceea de multe ori găsim mai multe surse din care ne putem informa corect.
Notă: Acești termeni au multiple semnificații dependente de limbajul de programare, context etc. Nu vom da definiții exacte, ci vom oferi explicații sumare ca să vă faceți o idee despre diferența dintre aceste noțiuni.
C
, avem 2 moduri să verificăm documentația / să căutăm în ea:Google it!
: de exemplu, dacă căutăm pe Google man scanf
sau man stdio
/ man stdio.h
între primele (2-3) rezultate vom găsi linkuri de man7.org sau linux.die.net/man care ne duc către documentația pentru funcția scanf
/ biblioteca stdio
.terminal
: de exemplu, dacă nu avem acces la internet sau la un browser, putem rula în terminal man scanf
sau man stdio
/ man stdio.h
pentru a deschide în terminal documentația pentru funcția scanf
/ biblioteca stdio
.man ls
sau man grep
.Vom parcurge niște pași orientativi și vom exemplifica fiecare pas pe o problemă dată.
Problemă propusă: Avem o listă de studenți, iar pentru fiecare student știm numele și grupa. Se cere o listă sortată a studenților după următoarele criterii: lexicografic după grupă, iar în caz de egalitate sortăm lexicografic după nume. Limbajul care trebuie folosit este limbajul C.
Pasul 1: Identificăm problema de rezolvat
În acest pas este important să formulăm problema cât mai generic
În această problemă, un mod natural de rezolvare ar fi să stocăm studenții într-un vector de structuri. Problema de rezolvat este, în mod generic, sortarea unui vector.
Pasul 2: Căutăm în documentație ce ne oferă limbajul pentru a rezolva problema dată
Vom folosi paginile man
. Observăm, la o căutare pe internet sau în documentație, că există funcția qsort documentată în man7 și linux.die. Consultăm amândouă sursele dacă este cazul (de exemplu, prima are explicații mai bune, dar doar a doua are exemplu de utilizare).
Să citim descrierea acestei funcții:
The qsort() function sorts an array with _nmemb_ elements of size _size_. The _base_ argument points to the start of the array. The contents of the array are sorted in ascending order according to a comparison function pointed to by _compar_, which is called with two arguments that point to the objects being compared.
Semnătura funcției este:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
Prin urmare, această funcție va primi un vector a cărui adresă de start este în variabila base, va primi numărul de elemente (nmemb) și dimensiunea unui element (size), precum și o funcție comparator (compare).
Pasul 3: Testăm această funcționalitate pe un exemplu simplu
Anumite documentații oferă deja exemple pentru funcționalitățile oferite, așa cum se întâmplă și pentru man qsort
Aceste exemple pot fi uneori rulate direct pe platforma lor. Este util să faceți asta și să schimbați datele pentru a vedea exact cum se comportă acea funcționalitate.
În acest caz, exemplul de pe linux.die.net/man/3/qsort nu poate fi rulat pe platformă, așa că vom face un program în care vom rula exemplul oferit de documentație. Facem acest lucru pentru a ne asigurăm că funcția face ce ne dorim și pentru a înțelege exact cum să o folosim pentru problema noastră. Luăm exact codul de pe acea pagină, care sortează string-uri primite de la linia de comandă:
// example.c: code sample from https://linux.die.net/man/3/qsort #include <stdio.h> #include <stdlib.h> #include <string.h> static int cmpstringp(const void *p1, const void *p2) { /* The actual arguments to this function are "pointers to pointers to char", but strcmp(3) arguments are "pointers to char", hence the following cast plus dereference */ return strcmp(* (char * const *) p1, * (char * const *) p2); } int main(int argc, char *argv[]) { int j; if (argc < 2) { fprintf(stderr, "Usage: %s <string>...\n", argv[0]); exit(EXIT_FAILURE); } qsort(&argv[1], argc - 1, sizeof(char *), cmpstringp); for (j = 1; j < argc; j++) puts(argv[j]); return 0; }
Compilăm și rulăm codul:
$ gcc -Wall example.c -o example $ ./example mama are mere are mama mere
Observăm că a sortat cele 3 string-uri primite ca argumente în linia de comandă, exact cum ne așteptam. Trecem la pasul următor.
Pasul 4: Integrăm în proiectul nostru
În acest pas vom scrie mai întâi o funcție comparator:
static int cmp_students(const void* first_ptr, const void* second_ptr) { student_t* first = (student_t *) first_ptr; student_t* second = (student_t *) second_ptr; int cmp_groups = strcmp(first->group, second->group); if (cmp_groups != 0) return cmp_groups; return strcmp(first->name, second->name); }
Acum vom scrie codul complet și il vom testa:
// sort_students.c #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char *group; char *name; } student_t; static int cmp_students(const void* first_ptr, const void* second_ptr) { student_t* first = (student_t *) first_ptr; student_t* second = (student_t *) second_ptr; int cmp_groups = strcmp(first->group, second->group); if (cmp_groups != 0) return cmp_groups; return strcmp(first->name, second->name); } int main(void) { student_t students[] = { { "312CA", "Popescu Ion" }, { "311CA", "Dumitrescu Mihai" }, { "315CA", "Almasan Maria" }, { "313CA", "Grigorescu Alex" }, { "311CA", "Barbu Gigel" }, { "312CA", "Vadim Tudor" }, { "312CA", "Florescu Teodora" }, { "314CA", "Fodor Maria" }, { "314CA", "Alexandrescu Matei" }, { "313CA", "Radu Ioana" }, { "311CA", "Mugurel Alexandra" }, { "315CA", "Ungureanu Andreea" } }; int no_students = sizeof(students) / sizeof(students[0]); qsort(students, no_students, sizeof(students[0]), cmp_students); for (int i = 0; i < no_students; i++) { printf("%s, %s\n", students[i].group, students[i].name); } return 0; }
Foarte important: Nu uitați să testați să vedeți dacă problema voastră este rezolvată corect!
Compilăm și rulăm codul:
$ gcc -Wall -Wextra sort_students.c -o sort_students $ ./sort_students 311CA, Barbu Gigel 311CA, Dumitrescu Mihai 311CA, Mugurel Alexandra 312CA, Florescu Teodora 312CA, Popescu Ion 312CA, Vadim Tudor 313CA, Grigorescu Alex 313CA, Radu Ioana 314CA, Alexandrescu Matei 314CA, Fodor Maria 315CA, Almasan Maria 315CA, Ungureanu Andreea
Prin platforme de tip forum ne referim la: Stackoverflow, Quora, Ask Ubuntu, și alele.
În continuare vă vom oferi un exemplu de postare de pe Stackoverflow în care primul răspuns este unul greșit, deși este votat de mulți utilizatori: link.
În primul răspuns, este specificat că următoarele linii de cod sunt aproximativ același lucru din punct de vedere practic:
// varianta 1 artist = (char *) malloc(0); // varianta 2 artist = NULL;
Cu toate acestea, dacă rulați următorul cod pe diverse platforme:
int *i = malloc(0); *i = 100; printf("%d\n", *i);
… surprinzător poate, dar pe anumite platforme veți obține rezultatul 100.
În schimb, dacă scrieți:
int *i = NULL; *i = 100; printf("%d\n", *i);
cel mai probabil veți primi Segmentation fault.
Așadar, practic vorbind, cele 2 coduri se comportă diferit. Deși primul cod pare că merge în anumite cazuri, în realitate el va ascunde un acces invalid la memorie pentru că se încearcă scrierea la o adresă de memorie nealocată. Să rulam cu valgrind acest program:
// bad_example.c #include <stdio.h> #include <stdlib.h> int main(void) { int *i = malloc(0); *i = 100; printf("%d", *i); return 0; }
valgrind --leak-check=full ./bad_example ==51943== Memcheck, a memory error detector ==51943== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==51943== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info ==51943== Command: ./bad_example ==51943== ==51943== Invalid write of size 4 ==51943== at 0x109187: main (in /path/to/bad_example) ==51943== Address 0x4a5a040 is 0 bytes after a block of size 0 alloc'd ==51943== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==51943== by 0x10917E: main (in /path/to/bad_example) ==51943== ==51943== Invalid read of size 4 ==51943== at 0x109191: main (in /path/to/bad_example) ==51943== Address 0x4a5a040 is 0 bytes after a block of size 0 alloc'd ==51943== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==51943== by 0x10917E: main (in /path/to/bad_example) ==51943== 100==51943== ==51943== HEAP SUMMARY: ==51943== in use at exit: 0 bytes in 1 blocks ==51943== total heap usage: 2 allocs, 1 frees, 1,024 bytes allocated ==51943== ==51943== 0 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==51943== at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==51943== by 0x10917E: main (in /path/to/bad_example) ==51943== ==51943== LEAK SUMMARY: ==51943== definitely lost: 0 bytes in 1 blocks ==51943== indirectly lost: 0 bytes in 0 blocks ==51943== possibly lost: 0 bytes in 0 blocks ==51943== still reachable: 0 bytes in 0 blocks ==51943== suppressed: 0 bytes in 0 blocks ==51943== ==51943== For lists of detected and suppressed errors, rerun with: -s ==51943== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
Observăm, așa cum ne-am aștepta, că avem un leak de memorie. Mai mult, accesăm locații de memorie pe care nu ar trebui, odată la atribuire, odată la afișare.
De ce uneori se afișează 100?
Răspunsul pe scurt este că malloc, în anumite situații, în spate alocă extra spațiu față de cât îi spunem noi. Dar acel spațiu nu este un spațiu pe care noi să-l putem folosi (nu ne garantează nimeni nici faptul că este alocat acel extra spațiu), iar accesul lui va conduce la comportament nedefinit, deoarece acolo pot fi stocate alte date. În anumite cazuri, se alocă acel spațiu extra unde se va stoca valoarea 100, iar mai apoi se va citi valoarea și se va afișa. Acesta este și motivul pentru care, în aparență, programul pare că merge. Mai multe detalii veți afla la cursul de SO.
Din nou, acest acces la memorie este incorect, nu faceți așa ceva deoarece aveți comportament nedefinit. Pentru a evita aceste situații vă recomandăm să utilizați tool-uri de memorie, cum ar fi valgrind.
Vă invităm să citiți toată conversația What’s the point of malloc(0)?, oferă niște puncte de vedere interesante despre acest subiect.
Concluzie: mare atenție când folosiți aceste platforme. Recomandarea noastră este să căutați în mai multe locuri răspunsuri și să testați orice cod folosiți din surse externe înainte de a-l integra în aplicațiile voastre.