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
- Tipuri de date și declarații
- Specificatori de format în
printf - Împărțirea întreagă — capcana #1
- Operatori pe biți și complementul față de 2
- Incrementare / decrementare:
++ivs.i++ - Tablouri și pointeri
- Tablouri multidimensionale
- Șiruri de caractere
- Transmiterea parametrilor
- Recursivitate
- Variabile
static - Memorie dinamică: leak, dangling, free
- 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:
Rețeta pentru m &= ~k + c (întrebarea recurentă)¶
- Calculează
~k + cca număr întreg cu semn, folosind~k = −(k+1). -
Rezultatul e negativ → transformă-l în binar pe 8 biți, prin complement față de 2:
−Npe 8 biți =256 − N(te interesează doar ultimul octet, fiindcămesteunsigned char). -
Fă
ANDbit cu bit întremș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ă:
| 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ă cusizeof(tip):p + 1avansează cu un element, nu cu un octet.- Dacă
p = &a[k], atuncip[j] == a[k + j]. ← formula care rezolvă întrebarea cuaptr[31].
Indexare indirectă¶
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:
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]:
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, c → Abc.
9. Transmiterea parametrilor¶
C: totul se transmite prin valoare¶
Pentru a modifica originalul în C pur, transmiți adresa:
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:
- Identifică cazul de bază — când se oprește?
- Identifică pasul — cum se apropie argumentul de cazul de bază?
- 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¶
Varianta A (2019 și 2022):
- a. k = 68 __ m = 20 · b. k = 87 __ m = 20 · c. k = 57 __ m = 34 · d. k = 91 __ m = 26 · e. k = 87 __ m = 46Varianta 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);
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:
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 = 20 → b
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:
Rezultat: k = 119 __ m = 32 → e
Cele trei lucruri de reținut
~x = −(x+1)— scurtează dramatic calculul; nu inversa biții manual.~kse calculează peint(32 de biți), darmesteunsigned char, deci contează doar ultimul octet al rezultatului.- Citește ordinea liniilor! În 2020
mse calculează înainte caksă fie modificat. Distractorii sunt construiți exact pe această greșeală.
C2 · Parametru transmis prin referință (int &y)¶
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 = 2 → c î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 -= x → b = 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ă șixeste referință.b = 2→ greșeala de a crede căyeste copie.c = 1→ confuzia întrexî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¶
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ă.
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:
(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
index = i·D2·D3 + j·D3 + k+ offsetdinaptr[offset]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¶
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).
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 între0xC000 = 49152și0xD000 = 53248. - Singurele variante din acel interval: d) 51118 și e) 50862.
- Ultimii biți sunt
1110→ numărul este par, iarmod 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¶
- 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 * float → float |
10.0 |
| 3 | 10.0 + 32 |
float + int → float |
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¶
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));
}
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¶
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:
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:
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¶
- a. 80 b. 79 c. 81 d. Eroare de compilareRăspuns și explicație
Răspuns: a) 80
++b este pre-incrementare: b devine 70, iar valoarea expresiei este cea de după incrementare (70).
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)¶
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);
}
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:
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:
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¶
- 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ă:
do…whileexecută corpul întâi, apoi testează → afișează cel puțin o dată.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ăxse incrementează înainte de comparație (++x <= 1→2 ≤ 1fals imediat). - d)
Nimic— dacă ai confundado…whilecuwhile(unwhile (x++ <= 1)simplu ar afișa tot1 2, dar dacă condiția ar fi falsă din start,whilen-ar afișa nimic, iardo…whiletot 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¶
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));
}
7 20 · b) 5 9 · c) 5 20 · d) 8 20
Varianta 2023 / 2025 — șirul cu spații:
- 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":
2023 / 2025 — "Hello \0 Hi" — atenție la spațiul dinaintea lui \0:
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ă¶
#include <stdio.h>
void f() {
static int i = 3;
printf("%d ", i);
if (--i) f();
}
void main() {
f();
}
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ă¶
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¶
Care este specificatorul de format pentru printarea unei valori de tip double?
- a.
%lfb.%dc.%.2xd.%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¶
Care este specificatorul de format utilizat pentru afișarea unei valori de tip char?
- a.
%lfb.%dc.%.2xd.%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¶
- a. 2 b. 0 c. 1 d. Eroare de compilareRăspuns și explicație
Răspuns: c) 1
Se rezolvă dinspre interior spre exterior, ca la funcțiile compuse:
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++¶
- a. AbC b. Abc c. Bc d. Eroare de compilare
Răspuns și explicație
Răspuns: b) Abc
Două idei:
-
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. -
*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șabc). - d) — codul este valid. (Nuanță:
char *s = "Abc"indică spre un literal de tip read-only; a scrie prinsar fi UB, dar aici doar citim și mutăm pointerul — perfect legal.)
C18 · Ce tip de eroare există în program¶
#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);
}
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ă:
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¶
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);
}
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
(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¶
Care este valoarea returnată în urma apelului f(500, 1000)?
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.
Pasul 2 — care este valoarea comună la final? Se întâlnesc la mijlocul intervalului:
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:
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¶
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));
}
Răspuns și explicație
Răspuns: a) 343400
Observația-cheie: ambele ramuri adaugă pătratul unui număr par!
npar → adaugăn²(n este par)nimpar → 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:
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
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¶
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);
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ă:
Linia 1 — strcpy(aux, s1 + 2);
s1 + 2 indică poziția 2 → subșirul "misATM".
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:
Linia 3 — strcpy(s1, s1 + 5);
s1 + 5 indică poziția 5 din "AdmisATMATM":
Se copiază "ATMATM" la începutul lui s1:
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 |