Comportament nedefinit (undefined behavior) în C

Situațiile pentru care standardul C nu spune ce se întâmplă: programul poate merge, poate da rezultate greșite sau se poate opri.

Sintaxă
// Exemple de comportament nedefinit:
t[n]              // index în afara tabloului
*p                // p este NULL sau eliberat
INT_MAX + 1       // depășire la întregi cu semn
int x; x + 1      // variabilă neinițializată
i = i++ + 1;      // aceeași variabilă modificată de două ori fără ordine

Ce face Comportament nedefinit (undefined behavior)

Standardul C descrie ce face fiecare construcție corectă. Pentru unele operații greșite spune doar că „comportamentul este nedefinit” (undefined behavior, UB): compilatorul nu are nicio obligație. Programul poate afișa ce te aștepți, o valoare oarecare, se poate opri sau se poate comporta diferit cu alt compilator sau cu altă optimizare (-O2).

Cel mai periculos este că UB nu dă neapărat o eroare: optimizatorul presupune că UB nu apare niciodată și poate elimina verificări pe care le-ai scris. De exemplu, presupunând că x + 1 nu depășește niciodată INT_MAX, poate considera că x + 1 > x este mereu adevărat.

Cele mai frecvente cazuri: index în afara tabloului, dereferențierea lui NULL sau a unui pointer eliberat, depășirea la întregi cu semn, citirea unei variabile neinițializate, împărțirea la zero, modificarea unui literal de tip șir, modificarea aceleiași variabile de două ori într-o expresie fără ordine definită. Unelte care ajută: avertismentele (-Wall -Wextra) și sanitizerele (-fsanitize=address,undefined).

Exemple

Exemplul 1: Depășire la întregi: varianta corectă

Înainte de adunare se verifică dacă rezultatul încape în int. Pentru unsigned, depășirea este definită (se reia de la 0); doar la tipurile cu semn este UB. În C23 poți folosi și ckd_add din <stdckdint.h>.

#include <stdio.h>
#include <limits.h>

int aduna_sigur(int a, int b, int *rezultat) {
    if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) return 0;
    *rezultat = a + b;
    return 1;
}

int main(void) {
    int r;
    if (aduna_sigur(2000000000, 100000000, &r)) printf("suma = %d\n", r);
    else printf("suma nu încape în int\n");

    unsigned int u = UINT_MAX;
    u = u + 1;   // definit: se reia de la 0
    printf("UINT_MAX + 1 = %u\n", u);
    return 0;
}

Rezultat

suma = 2100000000
UINT_MAX + 1 = 0

Exemplul 2: Index în afara tabloului (nu rula așa)

Bucla merge până la i <= 3, deci citește și note[3], care nu există. Compilatorul nu avertizează aici, iar programul pare să meargă: pe calculatorul de test, ultimul rând a fost 2, o valoare care nu există în tablou. Pe alt calculator poate fi orice altceva, iar programul se poate și opri.

#include <stdio.h>

int main(void) {
    int note[3] = {9, 7, 10};
    for (int i = 0; i <= 3; i++) {   // GREȘIT: trebuie i < 3
        printf("%d\n", note[i]);
    }
    return 0;
}

Rezultat posibil (comportament nedefinit)

9
7
10
2

Exemplul 3: Prinderea UB cu sanitizerul

Compilat cu -fsanitize=undefined, programul raportează depășirea în timpul rulării, cu fișierul și rândul. Folosește sanitizerele în timpul dezvoltării și al testelor.

// compilare: clang -std=c23 -fsanitize=undefined depasire.c -o depasire
#include <stdio.h>
#include <limits.h>

int main(void) {
    int x = INT_MAX;
    x = x + 1;   // GREȘIT: depășire la int cu semn
    printf("%d\n", x);
    return 0;
}

Ieșire în terminal

depasire.c:7:11: runtime error: signed integer overflow: 2147483647 + 1 cannot be represented in type 'int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior depasire.c:7:11
-2147483648

Greșeli frecvente

  • Crezi că dacă programul „merge” pe calculatorul tău, codul este corect. UB poate apărea doar cu alt compilator, altă optimizare sau alte date.
  • Verifici depășirea după ce s-a produs (if (a + b < a) pentru int). Depășirea în sine este deja UB, iar compilatorul poate elimina testul. Verifică înainte de operație.
  • Scrii i = i++ + 1; sau a[i] = i++;. Variabila este modificată și citită fără o ordine definită; desparte în instrucțiuni separate.
  • Ignori avertismentele compilatorului. Multe cazuri de UB (variabile neinițializate, indecși constanți greșiți, formate printf greșite) sunt semnalate de -Wall -Wextra.

Comenzi înrudite

  • Tablouri (array): Un șir de elemente de același tip, așezate unul după altul în memorie și accesate prin index, de la 0.
  • Pointeri (& și *): O variabilă care păstrează adresa altei variabile; cu * citești sau modifici valoarea de la acea adresă.
  • free: Eliberează un bloc alocat cu malloc, calloc sau realloc, ca memoria să poată fi refolosită.
  • ckd_add, ckd_sub, ckd_mul: Adunare, scădere și înmulțire cu verificarea depășirii: funcțiile din stdckdint.h spun dacă rezultatul a încăput în tip.
  • Compilarea cu gcc și clang: Cum transformi codul C într-un program executabil cu gcc sau clang și ce fac opțiunile folosite cel mai des.
  • Operatori aritmetici (+ - * / % ++ --): Adunare, scădere, înmulțire, împărțire, restul împărțirii, atribuiri compuse și incrementare cu ++ / –.
Comunitate & Schimb de cunoștințe

Întrebări & Exemple de Cod: Comportament nedefinit (undefined behavior)

Ai o nelămurire despre utilizarea comenzii Comportament nedefinit (undefined behavior) sau ai descoperit un truc / fragment de cod util? Distribuie-l cu ceilalți programatori!

💬

Fii primul care pune o întrebare sau distribuie un fragment de cod / prompt!

Nu au fost adăugate încă întrebări sau exemple de cod pentru această pagină. Dacă ai întâlnit o problemă sau ai un snippet util, adaugă-l mai jos pentru a-i ajuta pe ceilalți.

Comunitate Adaugă o întrebare sau distribuie un cod

Moderare & Siguranță:

Pentru a menține calitatea și securitatea informațiilor, fiecare întrebare sau fragment de cod/prompt este analizat (manual sau prin moderare asistată de AI) înainte de publicare.