Sari la conținut

Limbajul de programare C

Ponderea la examen: al doilea capitol ca mărime — 9 din 30 în 2025, 5 din 18 în 2023, 10 din 30 în 2022, 7 din 18 în 2019 și 2020. Împreună cu Rețelele, acest capitol decide examenul.

Cum se dau întrebările

Aproape toate sunt de forma „Ce va afișa următorul program?”. Nu ai nevoie să scrii cod — ai nevoie să simulezi mental corect. Partea I îți dă regulile, Partea a II-a le antrenează.


Cuprins

Partea I — Teorie

  1. Tipuri de date și declarații
  2. Specificatori de format în printf
  3. Împărțirea întreagă — capcana #1
  4. Operatori pe biți și complementul față de 2
  5. Incrementare / decrementare: ++i vs. i++
  6. Tablouri și pointeri
  7. Tablouri multidimensionale
  8. Șiruri de caractere
  9. Transmiterea parametrilor
  10. Recursivitate
  11. Variabile static
  12. Memorie dinamică: leak, dangling, free
  13. Precedența operatorilor

Partea a II-a — Toate întrebările, rezolvate (22 de întrebări unice)


Partea I — Teorie

1. Tipuri de date și declarații

Tip Dimensiune tipică Interval (cu semn)
char 1 octet (8 biți) −128 … 127
unsigned char 1 octet 0 … 255
short 2 octeți −32 768 … 32 767
int 4 octeți −2 147 483 648 … 2 147 483 647
unsigned int 4 octeți 0 … 4 294 967 295
long long 8 octeți ±9,2 · 10¹⁸
float 4 octeți ~7 cifre semnificative
double 8 octeți ~15 cifre semnificative

Modificatorii — cine se poate combina cu cine

Modificator Se aplică la Exemple valide
signed / unsigned doar tipuri întregi: char, short, int, long, long long unsigned char c; unsigned int n; signed char s;
short / long int (și long double) short int a; long long e;

Regula de aur

signed și unsigned NU se pot aplica tipurilor cu virgulă mobilă. unsigned float · unsigned double · signed float Motivul: reprezentarea IEEE 754 are un bit de semn dedicat — nu există varianta „fără semn”.

Declarații valide: int b; · unsigned char c; · long long e; · unsigned d; (= unsigned int)

2. Specificatori de format în printf

Specificator Tip Observații
%d / %i int zecimal cu semn
%u unsigned int zecimal fără semn
%c char un singur caracter
%s char * șir terminat cu \0
%f float / double 6 zecimale implicit
%lf double forma explicită pentru double
%.2f 2 zecimale (42.00)
%x / %X întreg hexazecimal (mic/mare)
%o întreg octal
%p pointer adresă
%% caracterul %
%zu size_t corect pentru strlen, sizeof

Perechile testate la examen

char%c · double%lf · int%d · șir → %s

Precizare tehnică: în printf, float este promovat automat la double, așa că %f funcționează și pentru double. %lf este forma explicit corectă și singura acceptată în scanf pentru double. La grilă, când opțiunile sunt %lf / %d / %.2x / %s, răspunsul pentru double este %lf.

3. Împărțirea întreagă — capcana #1

Dacă ambii operanzi sunt întregi, / face împărțire ÎNTREAGĂ (trunchiază spre zero). Tipul rezultatului este decis înainte de orice atribuire sau conversie ulterioară.

9 / 4          2      (nu 2.25!)
9 / 4.0        2.25   (unul e double  împărțire reală)
(float)9 / 4   2.25
9 % 4          1      (restul)

Capcana clasică:

float c = 5.0;
printf("%.2f", (9/4) * c + 32);
//               ▲
//               └── se evaluează PRIMUL, cu întregi: 9/4 = 2
//  2 * 5.0 = 10.0  →  10.0 + 32 = 42.0  →  afișează "42.00"

Faptul că c este float nu ajută — conversia la float are loc abia la înmulțire, după ce 9/4 a fost deja trunchiat.

Reflexul de examen

Ori de câte ori vezi o împărțire între două constante întregi într-o expresie cu float/double, calculeaz-o separat, cu întregi, prima dată.

4. Operatori pe biți și complementul față de 2

Operator Nume Efect
& AND 1 doar dacă ambii biți sunt 1
\| OR 1 dacă cel puțin unul e 1
^ XOR 1 dacă biții diferă
~ NOT (complement pe biți) inversează toți biții
<< shift stânga x << n = x · 2ⁿ
>> shift dreapta x >> n = x / 2ⁿ (întreg)

Tabelul hexa → binar (de învățat pe de rost)

Hexa Binar Hexa Binar
0 0000 8 1000
1 0001 9 1001
2 0010 A 1010
3 0011 B 1011
4 0100 C 1100
5 0101 D 1101
6 0110 E 1110
7 0111 F 1111

Promovarea la int — capcana ascunsă

Orice char sau short este promovat la int (32 de biți) înainte de o operație aritmetică sau pe biți. Aceasta este cauza celor mai multe greșeli la întrebările cu ~.

unsigned char k = 0x57;   // 87
~k                        // NU este 0xA8 pe 8 biți!
                          // k se promovează la int: 0x00000057
                          // ~ dă 0xFFFFFFA8 = -88 (int cu semn)

Complementul față de 2 pe scurt

  • ~x = inversarea tuturor biților.
  • ~x + 1 = −x~x = −x − 1~x = −(x + 1)

Această identitate scurtează enorm calculele:

~87      = -(87+1) = -88
~87 + 12 = -88 + 12 = -76

Rețeta pentru m &= ~k + c (întrebarea recurentă)

  1. Calculează ~k + c ca număr întreg cu semn, folosind ~k = −(k+1).
  2. Rezultatul e negativ → transformă-l în binar pe 8 biți, prin complement față de 2: −N pe 8 biți = 256 − N (te interesează doar ultimul octet, fiindcă m este unsigned char).

  3. AND bit cu bit între m și acel octet.

Exemplu complet (k = 87, m = 31, expresia m &= ~k + 12):

~87 + 12 = -88 + 12 = -76
-76 pe 8 biți: 256 - 76 = 180 = 0xB4 = 1011 0100
m = 31       =                        0001 1111
                                AND ───────────
                                      0001 0100  = 0x14 = 20

5. Incrementare / decrementare: ++i vs. i++

Formă Nume Valoarea expresiei Efectul asupra variabilei
++i pre-incrementare valoarea după incrementare i crește cu 1
i++ post-incrementare valoarea dinainte de incrementare i crește cu 1
int a = 10, b = 69;
a + ++b     // ++b: b devine 70, expresia valorează 70 → 10 + 70 = 80
a + b++     // b++: expresia valorează 69, apoi b devine 70 → 10 + 69 = 79

Șablonul mental

Citește simbolurile de la stânga la dreapta. ++b → „întâi ++, apoi b” = mai întâi crește, apoi îți dă valoarea. b++ → „întâi b, apoi ++” = mai întâi îți dă valoarea, apoi crește.

În condiții de buclă:

int x = 1;
do
   printf("%d ", x);
while (x++ <= 1);
Iterație Se afișează Se testează Rezultat test x după test
1 1 1 <= 1 adevărat → continuă 2
2 2 2 <= 1 fals → ieșire 3

Afișează: 1 2. Reține că do…while execută corpul cel puțin o dată, înainte de primul test.

6. Tablouri și pointeri

  • Numele unui tablou „decade” în pointer la primul element: a&a[0].
  • a[i] este definit exact ca *(a + i). Aritmetica pe pointeri este scalată cu sizeof(tip): p + 1 avansează cu un element, nu cu un octet.
  • Dacă p = &a[k], atunci p[j] == a[k + j]. ← formula care rezolvă întrebarea cu aptr[31].

Indexare indirectă

int a[3] = {2, 0, 1};
printf("%d", a[a[0]]);
//            ▲▲▲▲▲
//            a[0] = 2  →  devine a[2]  →  1

Se rezolvă din interior spre exterior, ca la funcții compuse.

7. Tablouri multidimensionale

Un tablou T a[D1][D2][D3] este stocat liniar, în memorie continuă, pe ordinea rândurilor (row-major) — ultimul indice variază cel mai repede.

Formula indexului liniar:

index_liniar( a[i][j][k] )  =  i · (D2 · D3)  +  j · D3  +  k

Generalizat: fiecare indice se înmulțește cu produsul dimensiunilor de după el.

Ordinea de umplere a buclelor imbricate for i → for j → for k coincide exact cu ordinea liniară din memorie — deci elementul cu indexul liniar n este al (n+1)-lea atribuit.

Exemplu: int a[24][45][17], a[19][17][11]:

19 · (45 · 17)  +  17 · 17  +  11
= 19 · 765 + 289 + 11
= 14535 + 300
= 14835

Dacă bucla atribuie a[i][j][k] = contor-- pornind de la contor₀, atunci elementul cu index liniar n primește valoarea contor₀ − n.

8. Șiruri de caractere

Un „șir” în C = un tablou de char terminat de caracterul nul '\0'.

strlen vs. sizeof — distincția fundamentală

strlen(s) sizeof(s)
Ce este funcție, evaluată la rulare operator, evaluat la compilare
Ce măsoară caracterele până la primul \0 dimensiunea totală declarată, în octeți
Include \0? NU DA (numără tot tabloul)
char s[20] = "Hello\0Hi";
//            ▲▲▲▲▲ ▲
//            5 car. └── primul \0 → strlen se oprește aici
strlen(s) == 5      sizeof(s) == 20
char s[20] = "Hello \0 Hi";      // atenție la SPAȚIUL dinaintea lui \0
//            H e l l o ␣  = 6 caractere
strlen(s) == 6      sizeof(s) == 20

Citește caracter cu caracter, inclusiv spațiile.

Diferența dintre "Hello\0Hi" (→ 5) și "Hello \0 Hi" (→ 6) este un singur spațiu — și amândouă variante au apărut în examene diferite.

sizeof pe un tablou dă dimensiunea tabloului (20). sizeof pe un pointer (char *p) dă doar dimensiunea pointerului (8 pe 64 de biți) — capcană frecventă în alte contexte.

strcpy, strcat și pointerii în interiorul șirului

Funcție Efect
strcpy(dst, src) copiază src (inclusiv \0) peste dst, începând de la poziția indicată
strcat(dst, src) găsește \0-ul din dst și lipește src de acolo
strlen(s) lungimea până la \0
strcmp(a, b) 0 dacă sunt egale

Cheia întrebărilor de examen: buf + n este un pointer către poziția n din tablou (indexare de la 0). O copiere acolo suprascrie de la acea poziție încolo, păstrând primele n caractere.

char buf1[100] = "Examen";        // E x a m e n \0
char *strp1 = buf1 + 5;           //           ▲ poziția 5 = 'n'
strcpy(strp1, "abc");             // buf1 = "Exame" + "abc" = "Exameabc"
//              ▲▲▲▲▲ primele 5 caractere rămân

Notă de corectitudine

Copierea între zone care se suprapun (strcpy(s, s+5)) este, formal, comportament nedefinit în C standard. Aceste întrebări presupun implementarea uzuală, care copiază înainte, octet cu octet — iar rezultatul este cel calculat mai jos. Pentru grilă, aplică modelul „copiere înainte”.

Parcurgerea unui șir cu pointer

char *s = "Abc";
while (*s)                     // se oprește la '\0' (valoarea 0 = fals)
   printf("%c", *s++);         // afișează *s, apoi avansează pointerul

*s++ se citește: ++ are precedență mai mare decât *, dar este post-incrementare → se dereferențiază valoarea veche, apoi pointerul avansează. Afișează A, b, cAbc.

9. Transmiterea parametrilor

C: totul se transmite prin valoare

void f(int x) { x = 99; }     // modifică o COPIE; originalul nu se schimbă

Pentru a modifica originalul în C pur, transmiți adresa:

void f(int *x) { *x = 99; }   // f(&a) modifică pe a

C++: parametri prin referință (&)

int f(int x, int &y) {   // x = COPIE   |   y = ALIAS pentru variabila apelantului
   x = x + 1;            // afectează doar copia locală
   y -= x;               // MODIFICĂ variabila din apelant
   return x;
}

Regula

int x = copie (modificările nu se propagă) · int &y = alias (modificările se propagă). Prezența lui & în lista de parametri este singurul indiciu — caut-o prima dată.

10. Recursivitate

Metoda de rezolvare, în 3 pași:

  1. Identifică cazul de bază — când se oprește?
  2. Identifică pasul — cum se apropie argumentul de cazul de bază?
  3. Numără, nu desfășura tot lanțul. Caută tiparul: „de câte ori se aplică pasul?” și „ce se adună la fiecare pas?”

Șabloane frecvente

Formă Ce calculează Rezultat
f(x) = (x==1) ? 0 : 1 + f(x/2) de câte ori împart la 2 până ajung la 1 ⌊log₂ x⌋
f(x) = (x==0) ? 0 : c + f(x/10) numără cifrele lui x, adunând c la fiecare c × nr_cifre
f(a,b) = (a==b) ? val : 1 + f(a+p, b−p) numără pașii până la întâlnire (b−a)/(2p) pași

Trucul care economisește timp

Când recursivitatea adaugă o constantă per pas, nu simula lanțul — numără pașii și înmulțește. Când constanta depinde de o proprietate a cifrei (par/impar), numără câte cifre au fiecare proprietate.

Paritatea și ultima cifră

x % 2 depinde exclusiv de ultima cifră zecimală a lui x. Deci într-o recursie care face x / 10 la fiecare pas, testul x % 2 examinează, pe rând, fiecare cifră a numărului, de la dreapta la stânga.

11. Variabile static

O variabilă static dintr-o funcție:

  • este inițializată o singură dată, la prima intrare în funcție;
  • își păstrează valoarea între apeluri (trăiește pe toată durata programului, ca o globală, dar e vizibilă doar în funcție);
  • este stocată în segmentul de date, nu pe stivă.
void f() {
   static int i = 3;    // inițializat o singură dată!
   printf("%d ", i);
   if (--i) f();        // --i modifică ACEEAȘI variabilă la fiecare apel
}
Apel Afișează --i Continuă?
1 3 2 da (2 ≠ 0)
2 2 1 da (1 ≠ 0)
3 1 0 nu (0 = fals)

Rezultat: 3 2 1

Dacă i nu ar fi static, s-ar reinițializa la 3 la fiecare apel → recursie infinită → stack overflow.

12. Memorie dinamică: leak, dangling, free

Funcție Rol
malloc(n) alocă n octeți neinițializați pe heap; întoarce void * sau NULL
calloc(n, sz) alocă și inițializează cu zero
realloc(p, n) redimensionează
free(p) eliberează blocul

Șablonul corect:

int *v = (int *) malloc(n * sizeof(int));   // sizeof(int), nu n octeți!
if (v == NULL) { /* tratează eroarea */ }
/* … folosește v … */
free(v);
v = NULL;                                   // evită pointerul dangling

Cele două erori de recunoscut

Eroare Definiție Semnătura în cod
Memory leak (scurgere de memorie) s-a alocat memorie și s-a pierdut ultima referință către ea, fără free funcție care alocă și nu returnează pointerul; suprascrierea unui pointer alocat
Dangling pointer (pointer suspendat) pointerul indică o zonă deja eliberată sau ieșită din scop folosirea unui pointer după free; returnarea adresei unei variabile locale
Double free free pe același bloc de două ori
Buffer overflow scriere în afara blocului alocat

Exemplu de memory leak clasic:

int allocData(int n)
{
    int *data = (int *) malloc(n * sizeof(int));
    // ← nu există `return data;`  ⇒  adresa se pierde la ieșirea din funcție
}                                  //    memoria rămâne alocată, inaccesibilă = LEAK

13. Precedența operatorilor

De la cea mai mare la cea mai mică (extras util):

Nivel Operatori Asociativitate
1 () [] -> . i++ i-- (postfix)
2 ++i --i ! ~ + - (unar) * (deref) & sizeof (cast)
3 * / %
4 + -
5 << >>
6 < <= > >=
7 == !=
8 & (AND pe biți)
9 ^
10 \|
11 &&
12 \|\|
13 ?:
14 = += -= &= \|=

Cele mai frecvente surprize

+ și - au precedență mai mare decât << și >> → în x >> (p + 1 - n), parantezele sunt necesare oricum, dar ~0 << n se evaluează ca (~0) << n (unar ~ are precedență mai mare). - & (bit) are precedență mai mică decât ==if (x & 1 == 0) înseamnă x & (1 == 0), aproape sigur o eroare. - *s++ = *(s++) — post-incrementarea are precedență mai mare decât dereferențierea.


Partea a II-a — Toate întrebările, rezolvate

Sfat de metodă

La fiecare întrebare, scrie pe ciornă un tabel de trasare (variabilă → valoare la fiecare pas). Simularea mentală „din cap” este cea mai frecventă sursă de greșeli la acest capitol.


C1 · Operații pe biți cu unsigned char

2019 (I.1)2020 (I.1)2022 (Q25)recurentă, 3 ani

Varianta A (2019 și 2022):

unsigned char k = 0x57, m = 31;
k = 0x12 | k;
m &= ~k + 12;
printf("k = %d __ m = %d", k, m);
- a. k = 68 __ m = 20 · b. k = 87 __ m = 20 · c. k = 57 __ m = 34 · d. k = 91 __ m = 26 · e. k = 87 __ m = 46

Varianta B (2020) — atenție, ordinea liniilor este INVERSATĂ:

unsigned char k = 0x67, m = 41;
m &= ~k + 14;          // ← foloseşte k-ul ORIGINAL
k = 0x14 | k;
printf("k = %d __ m = %d", k, m);
- a. k = 168 __ m = 34 · b. k = 191 __ m = 26 · c. k = 111 __ m = 32 · d. k = 119 __ m = 33 · e. k = 119 __ m = 32

Răspuns și explicație

Varianta A → b) k = 87 __ m = 20 · Varianta B → e) k = 119 __ m = 32


VARIANTA A, pas cu pas.

Pasul 1 — k = 0x12 | k:

0x57 = 0101 0111
0x12 = 0001 0010
OR   ───────────
       0101 0111  = 0x57 = 87        ← k nu se schimbă!
(0x12 avea deja biții 1 și 4 aprinși în 0x57 → OR-ul nu adaugă nimic.)

Pasul 2 — m &= ~k + 12:

~k = ~87 = -(87+1) = -88             (identitatea ~x = -(x+1))
-88 + 12 = -76
-76 pe 8 biţi:  256 - 76 = 180 = 0xB4 = 1011 0100
m  = 31                             = 0001 1111
                              AND ───────────
                                      0001 0100 = 0x14 = 20

Rezultat: k = 87 __ m = 20b


VARIANTA B — ordinea liniilor este schimbată, iar m folosește k-ul nemodificat.

Pasul 1 — m &= ~k + 14, cu k = 0x67 = 103:

~103 = -(103+1) = -104
-104 + 14 = -90
-90 pe 8 biţi:  256 - 90 = 166 = 0xA6 = 1010 0110
m  = 41                              = 0010 1001
                               AND ───────────
                                       0010 0000 = 0x20 = 32

Pasul 2 — k = 0x14 | k:

0x67 = 0110 0111
0x14 = 0001 0100
OR   ───────────
       0111 0111 = 0x77 = 119

Rezultat: k = 119 __ m = 32e


Cele trei lucruri de reținut

  1. ~x = −(x+1) — scurtează dramatic calculul; nu inversa biții manual.
  2. ~k se calculează pe int (32 de biți), dar m este unsigned char, deci contează doar ultimul octet al rezultatului.
  3. Citește ordinea liniilor! În 2020 m se calculează înainte ca k să fie modificat. Distractorii sunt construiți exact pe această greșeală.

C2 · Parametru transmis prin referință (int &y)

2019 (I.2)2020 (I.2)recurentă

int f (int x, int &y) {
   x = x + 1;
   y -= x;
   return x;
}
void main( ) {
   int a = 1, b = 2;
   int c = f (a, b);
   printf ("a = %d, b = %d, c = %d\n", a, b, c);
}

Variante 2019: a) a=2, b=0, c=2 · b) a=1, b=2, c=2 · c) a=1, b=0, c=2 · d) a=2, b=2, c=2 · e) a=1, b=0, c=1 Variante 2020: a) a=2, b=0, c=2 · b) a=1, b=2, c=2 · c) a=2, b=2, c=2 · d) a=1, b=0, c=2 · e) a=1, b=0, c=1

Răspuns și explicație

Răspuns: a = 1, b = 0, c = 2c în 2019, d în 2020 (aceleași variante, ordine diferită — citește textul, nu litera!)

Cheia este diferența dintre cei doi parametri:

Parametru Declarat Legătura cu apelantul
x int x COPIE a lui a — modificările nu se propagă
y int &y ALIAS (referință) pentru b — modificările se propagă

Trasare:

Pas Acțiune a b x y (≡ b)
0 inițial 1 2
1 f(a, b) 1 2 1 (copie) alias către b
2 x = x + 1 1 2 2 2
3 y -= xb = 2 − 2 1 0 2 0
4 return x 1 0 2 c = 2

Afișează: a = 1, b = 0, c = 2

Analiza distractorilor:

  • a = 2 → greșeala de a crede că și x este referință.
  • b = 2 → greșeala de a crede că y este copie.
  • c = 1 → confuzia între x înainte și după incrementare.

Primul lucru pe care îl faci

La o astfel de întrebare: caută & în lista de parametri. Fără & → copie. Cu & → alias.


C3 · Tablou tridimensional și aritmetică pe pointeri

2019 (I.3)2020 (I.3)recurentă

void main( ) {
   int contor = 20019;              // 2020: 20020
   int a[24][45][17];
   int *aptr, i, j, k;
   for (i = 0; i < 24; i++)
      for (j = 0; j < 45; j++)
         for (k = 0; k < 17; k++)
            a[i][j][k] = contor--;
   aptr = &a[19][17][11];
   printf(" %d ", aptr[31]);
}

Variante 2019: a) 791 · b) 15807 · c) 6475 · d) 5153 · e) 9475 Variante 2020: a) 792 · b) 15808 · c) 6476 · d) 9476 · e) 5154

Răspuns și explicație

Răspuns: 2019 → d) 5153 · 2020 → e) 5154

Pasul 1 — indexul liniar al lui a[19][17][11]. Memoria este row-major: fiecare indice se înmulțește cu produsul dimensiunilor care îl urmează.

index = 19 · (45 · 17)  +  17 · 17  +  11
      = 19 · 765        +  289      +  11
      = 14535           +  300
      = 14835

Pasul 2 — aptr[31]. aptr indică elementul cu indexul liniar 14835. Regula pointerilor: dacă p = &a[k], atunci p[j] = a[k + j]. Deci aptr[31] este elementul cu indexul liniar:

14835 + 31 = 14866

(Aritmetica pe pointeri este scalată cu sizeof(int), dar tabloul fiind continuu în memorie, saltul trece firesc peste marginile „rândurilor”.)

Pasul 3 — ce valoare are elementul cu index 14866. Buclele parcurg tabloul exact în ordinea liniară (k variază cel mai repede, apoi j, apoi i). Elementul cu index liniar n primește valoarea contor₀ − n:

An contor₀ Calcul Rezultat
2019 20019 20019 − 14866 5153
2020 20020 20020 − 14866 5154

(Verificat prin simulare numerică.)

Rețeta în 3 rânduri

  1. index = i·D2·D3 + j·D3 + k
  2. + offset din aptr[offset]
  3. valoare = contor_inițial − index_total

Distractorii (791, 15807, 6475, 9475) provin din formule de indexare greșite — de ex. înmulțirea cu dimensiunile dinaintea indicelui, în loc de cele de după.


C4 · Conversie binar → zecimal

2019 (I.4)2020 (I.4)recurentă

Care este valoarea, în baza 10, a secvenței binare 1100011010101110?

  • a. 48848 b. 34985 c. 36458 d. 51118 e. 50862
Răspuns și explicație

Răspuns: e) 50862

Metoda rapidă: NU aduna 16 puteri ale lui 2. Grupează în cifre hexa (câte 4 biți, de la dreapta).

1100 0110 1010 1110
 │    │    │    │
 C    6    A    E        →  0xC6AE

Apoi convertești hexa → zecimal:

0xC6AE = C·16³ + 6·16² + A·16¹ + E·16⁰
       = 12·4096 + 6·256 + 10·16 + 14
       = 49152   + 1536  + 160   + 14
       = 50862

Verificare de bun-simț (30 de secunde):

  • Numărul are 16 biți și începe cu 11 → este între 0xC000 = 49152 și 0xD000 = 53248.
  • Singurele variante din acel interval: d) 51118 și e) 50862.
  • Ultimii biți sunt 1110 → numărul este par, iar mod 16 = 14. 51118 mod 16 = 14 , 50862 mod 16 = 14 — nu departajează.
  • Al doilea nibble: 0110 = 6. 50862 = 0xC6AE , 51118 = 0xC7AE ✘.

Reține doar 2¹⁰ = 1024 și tabelul hexa→binar

(vezi secțiunea 4). Gruparea în nibble-uri transformă o adunare de 16 termeni într-o înmulțire de 4.


C5 · Împărțire întreagă într-o expresie cu float

2019 (I.5)2020 (I.5)recurentă

int main( ) {
   float c = 5.0;
   printf("Valoarea este: %.2f ", (9/4) * c + 32);
   return 0;
}
- a. 42.00 b. 43.25 c. 0.00 d. Eroare la compilare e. Eroare la afișare

Răspuns și explicație

Răspuns: a) 42.00

Ordinea evaluării:

Pas Expresie Tip Rezultat
1 9 / 4 int / intîmpărțire întreagă 2 (nu 2.25!)
2 2 * c int * floatfloat 10.0
3 10.0 + 32 float + intfloat 42.0
4 %.2f 2 zecimale 42.00

Distractorul principal este b) 43.25 — rezultatul pe care l-ai obține dacă 9/4 ar da 2.25 (2.25 · 5 + 32 = 43.25). Este exact greșeala pe care o testează întrebarea.

Faptul că c este float nu salvează situația: promovarea la float are loc abia la înmulțire, adică după ce 9/4 a fost deja trunchiat la 2.

Ca să obții 43.25, ar fi trebuit scris 9/4.0, 9.0/4 sau (float)9/4.

Reflexul

Când vezi două constante întregi împărțite, evaluează întâi acea împărțire, cu întregi, indiferent de ce e în jur.


C6 · Recursivitate cu împărțire la 2

2019 (I.6)2020 (I.6)recurentă

int f (int x) {
   if (x == 1)  return 0;
   else return (1 + f (x / 2));
}
void main ( ) {
   int n = 2019;
   printf ("%d", f (n + 1));
}
- a. 0 b. 1 c. 10 d. 20 e. 32

Răspuns și explicație

Răspuns: c) 10

Ce calculează funcția: de câte ori poți împărți x la 2 (întreg) până ajungi la 1 — adică ⌊log₂ x⌋.

Atenție la argument: se apelează f(n + 1) = f(2020), nu f(2019).

Trasare:

Pas x x / 2
1 2020 1010
2 1010 505
3 505 252
4 252 126
5 126 63
6 63 31
7 31 15
8 15 7
9 7 3
10 3 1 → caz de bază, return 0

10 împărțiri → fiecare adaugă 1 → f(2020) = 10.

Verificare prin logaritm: 2¹⁰ = 1024 ≤ 2020 < 2048 = 2¹¹⌊log₂ 2020⌋ = 10

Scurtătura

Memorează puterile lui 2 până la 2¹⁶ (sunt în capitolul Formule) și răspunzi din prima, fără trasare: caută cea mai mare putere ≤ argument, exponentul ei este răspunsul.


C7 · strcpy cu pointeri în interiorul șirurilor

2019 (I.7)2020 (I.7)recurentă

void main() {
   char buf1[100] = "Examen";
   char buf2[100] = "Curs TAC 2019";     // 2020: "Curs TAC 2020"
   char *strp1 = buf1 + 5;
   char *strp2 = buf2 + 4;
   strcpy(strp1, buf2);
   strcpy(strp2, buf1);
   printf("%s\n", buf2);
}

Variante 2019: a) ExameCurs TAC 2019 · b) CursExameCurs TAC 2019 · c) Curs ExamenCurs TAC 2019 · d) ExamenCurs TAC 2019 · e) CursExam TAC 2019 Variante 2020: a) ExameCurs TAC 2020 · b) ExamenCurs TAC 2020 · c) CursExameCurs TAC 2020 · d) CursExam TAC 2020 · e) Curs ExamenCurs TAC 2020

Răspuns și explicație

Răspuns: CursExameCurs TAC 2019 (2019 → b) · CursExameCurs TAC 2020 (2020 → c)

Pasul 0 — unde indică pointerii. Indexarea începe de la 0:

buf1:  E  x  a  m  e  n  \0
       0  1  2  3  4  5
                   strp1 = buf1 + 5  → poziţia lui 'n'

buf2:  C  u  r  s  ␣  T  A  C  ␣  2  0  1  9  \0
       0  1  2  3  4
                strp2 = buf2 + 4  → poziţia spaţiului

Pasul 1 — strcpy(strp1, buf2): copiază "Curs TAC 2019" în buf1 începând de la poziția 5. Primele 5 caractere (E x a m e) rămân neatinse, iar n este suprascris:

buf1 = "Exame" + "Curs TAC 2019" = "ExameCurs TAC 2019"
        ▲▲▲▲▲    ▲
        rămân    de la poziţia 5

Pasul 2 — strcpy(strp2, buf1): copiază noul buf1 în buf2 începând de la poziția 4. Primele 4 caractere (C u r s) rămân:

buf2 = "Curs" + "ExameCurs TAC 2019" = "CursExameCurs TAC 2019"
        ▲▲▲▲    ▲
        rămân   de la poziţia 4

Se afișează: CursExameCurs TAC 2019 (verificat prin simulare octet cu octet)

Analiza distractorilor:

Variantă Greșeala corespunzătoare
ExameCurs TAC 2019 s-a afișat buf1 în loc de buf2
Curs ExamenCurs… (cu spațiu) s-a numărat buf2 + 4 ca fiind după spațiu (offset 5), sau buf1 + 5 ca păstrând și n
ExamenCurs TAC 2019 s-a numărat buf1 + 5 ca poziția 6 (după n)
CursExam TAC 2019 trunchiere arbitrară

Cele două întrebări pe care ți le pui

(1) Câte caractere rămân neatinse? — exact n, valoarea din buf + n. (2) Ce se afișează la final — buf1 sau buf2? Aici, buf2.


C8 · Pre-incrementare într-o expresie

2022 (Q21)

#include <stdio.h>
void main() {
   int a = 10, b = 69;
   printf("%d", a + ++b);
}
- a. 80 b. 79 c. 81 d. Eroare de compilare

Răspuns și explicație

Răspuns: a) 80

++b este pre-incrementare: b devine 70, iar valoarea expresiei este cea de după incrementare (70).

a + ++b  =  10 + 70  =  80

De unde vin distractorii:

Variantă De unde ar veni
b) 79 dacă ar fi fost a + b++ (post-incrementare) → 10 + 69 = 79
c) 81 dublă incrementare, greșeală de calcul
d) eroare a + ++b este sintaxă validă — spațiul dintre + și ++ este exact ce împiedică analizorul lexical să citească a++ +b. Fără spațiu (a+++b), regula „cel mai lung token” ar da a++ + b = 79.

Citește de la stânga la dreapta

++b = „întâi ++, apoi b” → întâi crește, apoi îți dă valoarea.


C9 · Extragerea unui câmp de biți (getbits)

2022 (Q22)

Considerând funcția definită alăturat, care este rezultatul apelului fct(15, 6, 5)?

#include <stdio.h>
unsigned fct(unsigned x, int p, int n) {
   return (x >> (p + 1 - n)) & ~(~0 << n);
}
- a. 3 b. 2 c. 0 d. 1

Răspuns și explicație

Răspuns: a) 3

Aceasta este funcția getbits din cartea K&R („The C Programming Language”): extrage n biți din x, începând de la poziția p, numărând de la dreapta (bitul 0).

Evaluare pas cu pas, cu x = 15, p = 6, n = 5:

Partea 1 — deplasarea:

p + 1 - n = 6 + 1 - 5 = 2
x >> 2 = 15 >> 2 = 1111₂ >> 2 = 11₂ = 3

Partea 2 — masca:

~0        = 1111…1111   (toţi biţii 1)
~0 << 5   = 1111…100000 (ultimii 5 biţi = 0)
~(~0 << 5)= 0000…011111 = 31    ← mască cu exact n = 5 biţi de 1

Partea 3 — AND:

3 & 31 = 00011 & 11111 = 00011 = 3

Rezultat: 3 (verificat numeric)

Șablonul de reținut, util și în alte contexte

~(~0 << n) = mască cu exact n biți de 1 la dreapta (echivalent cu (1 << n) - 1). - (x >> k) & mască = „mută biții doriți la dreapta, apoi taie restul”.

Aici masca (31) este mai lată decât ce a rămas din x după shift (3), deci AND-ul nu taie nimic — de aceea rezultatul e chiar x >> 2.


C10 · Buclă do…while cu post-incrementare

2022 (Q23)2023 (Q15)recurentă

#include <stdio.h>
void main() {
   int x = 1;
   do
      printf("%d ", x);
   while (x++ <= 1);
}
- a. 1 2 3 b. 1 2 c. 1 d. Nimic

Răspuns și explicație

Răspuns: b) 1 2

Două reguli se combină:

  1. do…while execută corpul întâi, apoi testează → afișează cel puțin o dată.
  2. x++ returnează valoarea veche pentru comparație, apoi incrementează.

Trasare:

Iterație Afișează Testul x++ <= 1 Valoarea comparată Rezultat x după test
1 1 x++ <= 1 1 ≤ 1 adevărat → continuă 2
2 2 x++ <= 1 2 ≤ 1 fals → ieșire 3

Afișează: 1 2

De unde vin distractorii:

  • c) 1 — dacă ai crede că x se incrementează înainte de comparație (++x <= 12 ≤ 1 fals imediat).
  • d) Nimic — dacă ai confunda do…while cu while (un while (x++ <= 1) simplu ar afișa tot 1 2, dar dacă condiția ar fi falsă din start, while n-ar afișa nimic, iar do…while tot ar afișa o dată).
  • a) 1 2 3 — o iterație în plus.

Regula de aur a lui do…while

Corpul rulează întotdeauna cel puțin o dată, oricare ar fi condiția.


C11 · strlen vs. sizeof cu \0 în interior

2022 (Q24)2023 (Q16)2025 (Q24)recurentă, 3 ani

Varianta 2022 — șirul fără spații:

#include <stdio.h>
#include <string.h>
void main() {
   char s[20] = "Hello\0Hi";
   printf("%d %d", strlen(s), sizeof(s));
}
- a) 7 20 · b) 5 9 · c) 5 20 · d) 8 20

Varianta 2023 / 2025 — șirul cu spații:

char s[20] = "Hello \0 Hi";
printf("%d %d", strlen(s), sizeof(s));
- a) 10 6 · b) 6 10 · c) 6 20 · d) 20 6

Răspuns și explicație

Răspuns: 2022 → c) 5 20 · 2023 / 2025 → c) 6 20

sizeof(s) = 20 în ambele cazuri. s este declarat char s[20]sizeof măsoară tabloul declarat, în octeți, indiferent de conținut. Este evaluat la compilare și nu îi pasă de \0.

strlen(s) numără caracterele până la PRIMUL \0:

2022 — "Hello\0Hi":

H  e  l  l  o  \0  H  i  \0
0  1  2  3  4   ▲
                └── strlen se opreşte aici  →  5

2023 / 2025 — "Hello \0 Hi"atenție la spațiul dinaintea lui \0:

H  e  l  l  o  ␣  \0  ␣  H  i  \0
0  1  2  3  4  5   ▲
                   └── strlen se opreşte aici  →  6

Diferența dintre cele două variante este UN SINGUR SPAȚIU.

Este exact tipul de detaliu pe care examenul îl exploatează. Numără caracterele unul câte unul, inclusiv spațiile, până la primul \0.

Tabel de sinteză:

strlen sizeof
Ce este funcție (rulare) operator (compilare)
Numără până la primul \0 toți octeții declarați
Include \0 nu da
"Hello\0Hi" în char[20] 5 20
"Hello \0 Hi" în char[20] 6 20

Notă tehnică: strlen întoarce size_t, iar formatul riguros este %zu. Cu %d majoritatea compilatoarelor afișează totuși corect pe platformele uzuale — grila presupune acest comportament.


C12 · Variabilă static într-o funcție recursivă

2022 (Q26)

#include <stdio.h>
void f() {
   static int i = 3;
   printf("%d ", i);
   if (--i) f();
}
void main() {
   f();
}
- a. 3 2 1 b. 3 2 1 0 c. 3 2 1 0 -1 d. 3 3 3

Răspuns și explicație

Răspuns: a) 3 2 1

Cheia este static: variabila i este inițializată o singură dată (la primul apel) și își păstrează valoarea între apeluri. Toate apelurile recursive lucrează pe aceeași variabilă.

Trasare:

Apel i la intrare Afișează --i Condiția if(--i) Continuă?
1 3 3 2 2 = adevărat
2 2 2 1 1 = adevărat
3 1 1 0 0 = fals stop

Afișează: 3 2 1

De unde vin distractorii:

Variantă Greșeala
b) 3 2 1 0 s-ar afișa dacă condiția ar fi if (i--) — atunci ultimul apel ar intra cu i = 0, l-ar afișa, și abia apoi ar ieși
c) 3 2 1 0 -1 dacă 0 ar fi considerat adevărat
d) 3 3 3 exact ce s-ar întâmpla dacă i NU ar fi static — s-ar reinițializa la 3 la fiecare apel → recursie infinită (stack overflow), nu doar trei afișări. Este distractorul care testează dacă ai văzut cuvântul static.

static = memorie între apeluri.

Prezența lui transformă complet semantica — caută-l primul. Reține și că în C, 0 este fals, orice altceva (inclusiv negativ) este adevărat.


C13 · Declarația incorectă

2022 (Q27)2023 (Q14)2025 (Q22)recurentă, 3 ani

Care dintre următoarele declarații este incorectă?

  • a. int b; b. unsigned char c; c. unsigned float d; d. long long e;
Răspuns și explicație

Răspuns: c) unsigned float d;

Regula: modificatorii signed / unsigned se aplică EXCLUSIV tipurilor întregi (char, short, int, long, long long).

De ce: tipurile cu virgulă mobilă (float, double, long double) folosesc reprezentarea IEEE 754, care are un bit de semn dedicat în format. Nu există o variantă „fără semn” a formatului — de aceea combinația este respinsă de compilator, nu doar nerecomandată.

Celelalte sunt perfect valide:

Declarație Valabilitate
a) int b; tipul întreg de bază
b) unsigned char c; char este un tip întreg → interval 0 … 255
d) long long e; întreg pe 64 de biți (standard din C99)

Alte combinații invalide, din același tipar: unsigned double, signed float, short float, long char.


C14 · Specificatorul de format pentru double

2022 (Q28)

Care este specificatorul de format pentru printarea unei valori de tip double?

  • a. %lf b. %d c. %.2x d. %s
Răspuns și explicație

Răspuns: a) %lf

l (litera „ell”, de la long) + f (de la float) = %lf, forma explicită pentru double.

Ceilalți sunt pentru tipuri complet diferite:

Tip
b) %d int
c) %.2x hexazecimal (întreg), cu 2 cifre
d) %s șir de caractere (char *)

Precizare: în printf, float este promovat implicit la double, deci %f afișează corect și un double. %lf este însă forma explicit corectă și obligatorie în scanf pentru double (acolo %f ar citi un float și ar corupe memoria). Dat fiind că %f nu apare printre variante, răspunsul este %lf.

Perechile de memorat: int%d · char%c · double%lf · char*%s · unsigned%u.


C15 · Specificatorul de format pentru char

2023 (Q17)2025 (Q25)recurentă

Care este specificatorul de format utilizat pentru afișarea unei valori de tip char?

  • a. %lf b. %d c. %.2x d. %c
Răspuns și explicație

Răspuns: d) %c

%c afișează un singur caracter. Distractorii sunt exact aceiași ca la C14, doar întrebarea diferă — citește tipul cerut, nu sări la răspunsul memorat.

Tip
a) %lf double
b) %d int ( cu un char, ar afișa codul ASCII: printf("%d", 'A')65)
c) %.2x hexazecimal
d) %c char

Perechea C14 / C15 este o capcană deliberată

Aceleași patru variante, întrebări diferite. Subliniază tipul din enunț înainte de a alege.


C16 · Indexare indirectă într-un tablou

2022 (Q29)2023 (Q18)2025 (Q26)recurentă, 3 ani

#include <stdio.h>
void main() {
   int a[3] = {2, 0, 1};
   printf("%d", a[a[0]]);
}
- a. 2 b. 0 c. 1 d. Eroare de compilare

Răspuns și explicație

Răspuns: c) 1

Se rezolvă dinspre interior spre exterior, ca la funcțiile compuse:

   a[a[0]]
      ▲▲▲▲
      │└┴┴── pasul 1:  a[0] = 2
      └───── pasul 2:  a[2] = 1

Tabloul:

index 0 1 2
valoare 2 0 1

a[0] = 2 → expresia devine a[2]1

De unde vin distractorii:

  • a) 2 — dacă te oprești la a[0] fără să faci a doua indexare.
  • b) 0 — dacă evaluezi a[1].
  • d) Eroare — indexarea cu o expresie este perfect legală în C.

Notă despre varianta tipărită din 2022. În foaia din septembrie 2022, declarația apare tipărită ca int a[3] = {2,,1}; — cu două virgule consecutive. Interpretată literal, aceasta este o eroare de sintaxă (varianta d). Însă:

  • în variantele 2023 și 2025 aceeași întrebare apare corect ca {2, 0, 1};
  • răspunsul marcat pe foaia din 2023 este c) 1.

⇒ Este o eroare de tipografie în varianta 2022, iar răspunsul așteptat este 1. Dacă la examen întâlnești din nou {2,,1}, alege 1, nu „eroare de compilare”.


C17 · Parcurgerea unui șir cu *s++

2022 (Q30)

#include <stdio.h>
void main() {
   char *s = "Abc";
   while (*s)
      printf("%c", *s++);
}
- a. AbC b. Abc c. Bc d. Eroare de compilare

Răspuns și explicație

Răspuns: b) Abc

Două idei:

  1. while (*s) — dereferențiază pointerul și testează caracterul. În C, '\0' are valoarea 0 = fals, deci bucla se oprește exact la terminatorul șirului. Toate celelalte caractere sunt nenule = adevărat.

  2. *s++++ (postfix) are precedență mai mare decât *, deci se citește *(s++). Fiind post-incrementare: se ia adresa veche, se dereferențiază (obținând caracterul curent), apoi pointerul avansează.

Trasare:

Iterație *s (test) Afișează s după
1 'A' ≠ 0 A 'b'
2 'b' ≠ 0 b 'c'
3 'c' ≠ 0 c '\0'
4 '\0' = 0 ieșire

Afișează: Abc — adică exact conținutul șirului, litere mici și mari păstrate.

De unde vin distractorii:

  • a) AbC — presupune o transformare de literă care nu există în cod.
  • c) Bc — ar rezulta dacă bucla ar sări primul caracter (*++s, pre-incrementare, ar afișa bc).
  • d) — codul este valid. (Nuanță: char *s = "Abc" indică spre un literal de tip read-only; a scrie prin s ar fi UB, dar aici doar citim și mutăm pointerul — perfect legal.)

C18 · Ce tip de eroare există în program

2025 (Q23)

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

int allocData(int n)
{
    int *data = (int *)malloc(n * sizeof(int));
}

void main()
{
    int *vec;
    vec = allocData(5);
    vec[4] = 20;
    free(vec);
}
- a. Nu există nicio eroare în program - b. Memory leak - c. Dangling pointer - d. Valoarea 20 nu poate fi atribuită elementului vec[4]

Răspuns și explicație

Răspuns: b) Memory leak

Unde este problema: funcția allocData alocă memorie, dar nu are instrucțiune return. La ieșirea din funcție, variabila locală data — singura care ținea adresa blocului — dispare de pe stivă. Blocul de 5 int rămâne alocat pe heap, dar nicio referință către el nu mai existămemory leak, prin definiție.

int allocData(int n)
{
    int *data = (int *) malloc(n * sizeof(int));
    // ← lipseşte `return (int) data;` sau, corect, `int *allocData(...) { ... return data; }`
}   // ▲ aici `data` iese din scop → adresa se pierde definitiv

De ce nu celelalte:

De ce nu
a) „nicio eroare” fals — pe lângă leak, funcția este declarată int dar nu returnează nimic, iar vec primește o valoare nedefinită
c) dangling pointer un pointer dangling indică memorie deja eliberată sau ieșită din scop. Aici vec nu ajunge niciodată să indice blocul alocat — problema este pierderea adresei, nu invalidarea ei
d) vec[4] este ultimul element valid dintr-un bloc de 5 (indici 0–4) — indexarea în sine ar fi corectă dacă alocarea ar fi fost returnată

Versiunea corectă:

int *allocData(int n)
{
    return (int *) malloc(n * sizeof(int));   // tip de retur corect + return
}

Cum recunoști rapid

Leak = „am alocat și am pierdut adresa” (lipsește return, sau se suprascrie pointerul, sau lipsește free). - Dangling = „folosesc un pointer după free()” sau „returnez adresa unei variabile locale”.


C19 · Recursivitate pe cifrele unui număr

2025 (Q27)

Dată fiind funcția recursivă rec, ce valoare returnează apelul rec(287349176)?

int rec(int x)
{
    if (x == 0)
        return 0;
    if (!(x % 2))
        return 1 + rec(x / 10);
    return 1000 + rec(x / 10);
}
- a. 6003 b. 4005 c. 3006 d. 5004

Răspuns și explicație

Răspuns: d) 5004

Observația care rezolvă totul: funcția face x / 10 la fiecare pas → parcurge cifrele de la dreapta la stânga. Iar x % 2 (paritatea numărului) depinde exclusiv de ultima cifră.

Deci funcția numără cifrele pare și impare ale numărului:

  • cifră pară (!(x%2) adevărat) → adaugă 1
  • cifră impară → adaugă 1000

Numărul: 2 8 7 3 4 9 1 7 6 (9 cifre)

Cifră Paritate Contribuție
2 pară 1
8 pară 1
7 impară 1000
3 impară 1000
4 pară 1
9 impară 1000
1 impară 1000
7 impară 1000
6 pară 1

Total: 4 cifre pare + 5 cifre impare

4 × 1  +  5 × 1000  =  4 + 5000  =  5004
(verificat prin execuție)

Citirea instantanee a rezultatului

În forma 5004, cifra miilor = numărul de cifre impare (5), iar unitățile = numărul de cifre pare (4). Toate variantele respectă acest tipar (6003 = 6 impare + 3 pare, 4005, 3006) — deci e suficient să numeri corect cifrele pare și impare, iar suma cifrelor din răspuns trebuie să fie 9 (numărul total de cifre). Verificare: 5+0+0+4 = 9

Atenție: !(x % 2) este adevărat când x este par (x % 2 = 0 → !0 = 1). Semnul ! inversează — nu-l sări.


C20 · Recursivitate cu doi parametri convergenți

2025 (Q28)

Care este valoarea returnată în urma apelului f(500, 1000)?

int f(int a, int b)
{
    if (a == b)
        return (a + b) / 2;
    else
        return 1 + f(a + 2, b - 2);
}
- a. 750 b. 1100 c. 875 d. 975

Răspuns și explicație

Răspuns: c) 875

Nu simula 125 de apeluri. Calculează în trei pași.

Pasul 1 — de câți pași e nevoie până a == b? La fiecare apel, a crește cu 2 și b scade cu 2 → diferența b − a scade cu 4.

diferenţa iniţială:  1000 - 500 = 500
paşi până la 0:      500 / 4 = 125

Pasul 2 — care este valoarea comună la final? Se întâlnesc la mijlocul intervalului:

a_final = 500 + 2 · 125 = 750
b_final = 1000 - 2 · 125 = 750     ✔ se verifică

Cazul de bază returnează (750 + 750) / 2 = **750**.

Pasul 3 — adună cei 125 de „1 +”. Fiecare dintre cele 125 de apeluri recursive adaugă câte 1:

rezultat = 750 + 125 = 875
(verificat prin execuție)

De unde vin distractorii:

Variantă Greșeala
a) 750 ai uitat să aduni cei 125 de 1 +
b) 1100 ai folosit un pas greșit (diferența scade cu 4, nu cu 2)
d) 975 pas greșit: 500/2 = 250 apeluri → 750 + 250 = 1000, sau alte combinații eronate

Formula generală

Pentru f(a,b) = (a==b) ? val : c + f(a+p, b−p): nr_paşi = (b − a) / (2p) · rezultat = val_la_întâlnire + c · nr_paşi

Funcționează doar dacă (b − a) este divizibil cu 2p. Altfel a „sare peste” b, condiția a == b nu se atinge niciodată și ai recursie infinită — merită verificat: 500 / 4 = 125 exact


C21 · Sumă recursivă de pătrate

2025 (Q29)

Se consideră programul de mai jos. Ce valoare afișează pe ecran?

#include <stdio.h>
int funct(int n)
{
    if (n == 0)
        return 0;
    if (n % 2 == 0)
        return n * n + funct(n - 1);
    return (n - 1) * (n - 1) + funct(n - 1);
}
void main()
{
    printf("%d", funct(101));
}
- a. 343400 b. 333400 c. 343300 d. 858500

Răspuns și explicație

Răspuns: a) 343400

Observația-cheie: ambele ramuri adaugă pătratul unui număr par!

  • n par → adaugă (n este par)
  • n impar → adaugă (n−1)² (n−1 este par)

Deci fiecare număr par apare de două ori în sumă: o dată de la el însuși, o dată de la impărul imediat următor.

Descompunerea pentru n de la 101 la 1:

Sursă Contribuții
n impar (101, 99, …, 3, 1) 100², 98², …, 2², 0²
n par (100, 98, …, 4, 2) 100², 98², …, 4², 2²

Suma totală:

S = (0² + 2² + 4² + … + 100²)  +  (2² + 4² + … + 100²)
  = 2 · (2² + 4² + … + 100²)          [0² = 0, nu contribuie]

Calculul:

2² + 4² + … + 100²  =  (2·1)² + (2·2)² + … + (2·50)²
                     =  4 · (1² + 2² + … + 50²)

Formula sumei pătratelor: 1² + 2² + … + k² = k(k+1)(2k+1) / 6

1² + … + 50² = 50 · 51 · 101 / 6 = 257550 / 6 = 42925

2² + 4² + … + 100² = 4 · 42925 = 171700

S = 2 · 171700 = 343400
(verificat prin execuție)

Verificare rapidă de plauzibilitate: varianta d) 858500 este exact 2,5 × răspunsul — ar rezulta din însumarea tuturor pătratelor (pare și impare). Variantele b) și c) diferă de răspuns doar cu 10 000 și, respectiv, 100 — sunt „aproape corecte”, construite ca să pedepsească o eroare mică de capăt de interval.

Formule de reținut

(și în capitolul Formule): 1 + 2 + … + k = k(k+1)/2 · 1² + 2² + … + k² = k(k+1)(2k+1)/6


C22 · strcpy + strcat cu suprapuneri

2025 (Q30)

Care va fi valoarea șirului s1 în urma executării secvenței de mai jos?

char s1[100] = "AdmisATM";
char aux[100];

strcpy(aux, s1 + 2);
strcat(s1, aux + 3);
strcpy(s1, s1 + 5);
- a. misATMMMMM b. isATM c. ATMATM d. ATMMMMM

Răspuns și explicație

Răspuns: c) ATMATM

Se rezolvă o linie pe rând, scriind explicit conținutul după fiecare pas.

Starea inițială:

s1:  A  d  m  i  s  A  T  M  \0
     0  1  2  3  4  5  6  7


Linia 1 — strcpy(aux, s1 + 2);

s1 + 2 indică poziția 2 → subșirul "misATM".

aux = "misATM"
       0 1 2 3 4 5

Linia 2 — strcat(s1, aux + 3);

aux + 3 indică poziția 3 din aux → subșirul "ATM". strcat caută \0-ul din s1 (poziția 8) și lipește acolo:

s1 = "AdmisATM" + "ATM" = "AdmisATMATM"
      ▲▲▲▲▲▲▲▲    ▲▲▲
      0…7          8 9 10

Linia 3 — strcpy(s1, s1 + 5);

s1 + 5 indică poziția 5 din "AdmisATMATM":

s1:  A  d  m  i  s  A  T  M  A  T  M  \0
     0  1  2  3  4  5  6  7  8  9 10
                    s1 + 5  →  "ATMATM"

Se copiază "ATMATM" la începutul lui s1:

s1 = "ATMATM"

Rezultat final: ATMATM (verificat prin simulare octet cu octet)


De unde vin distractorii:

Variantă Greșeala
a) misATMMMMM s-a oprit după linia 1 și s-a adăugat aleator
b) isATM s-a numărat s1 + 2 ca poziția 3
d) ATMMMMM eroare de numărare a offset-urilor

Metoda infailibilă

Desenează tabloul cu indici deasupra caracterelor și numără de la 0. buf + n = „sar peste primele n caractere”.

Notă: strcpy(s1, s1 + 5) copiază între zone care se suprapun — formal, comportament nedefinit în C standard. În practică, strcpy copiază înainte, octet cu octet, iar cum destinația este în urma sursei, rezultatul este cel de mai sus. Aceasta este interpretarea așteptată la examen.


Rezumatul capitolului — cele 15 reguli care rezolvă totul

# Regula
1 int / int = împărțire întreagă9/4 = 2
2 ~x = −(x + 1); char/short se promovează la int înainte de operațiile pe biți
3 ++b crește înainte, b++ crește după
4 do…while execută corpul cel puțin o dată
5 strlen = până la primul \0 · sizeof = tot tabloul declarat
6 buf + n = sar peste primele n caractere (indexare de la 0)
7 int &y = alias (se propagă) · int x = copie
8 static = valoarea persistă între apeluri
9 a[i]*(a+i); dacă p = &a[k] atunci p[j] = a[k+j]
10 Tablou 3D: index = i·D2·D3 + j·D3 + k
11 unsigned nu se aplică la float/double
12 %c → char · %lf → double · %d → int · %s → șir
13 ~(~0 << n) = mască cu n biți de 1
14 Memory leak = adresa alocată se pierde · Dangling = pointer folosit după free
15 4 biți = 1 cifră hexa — grupează, nu aduna puteri ale lui 2

Sisteme de operare · Index · ➡ Formule și conversii