nullptr în C

Constanta C23 pentru pointerul nul, cu tip propriu (nullptr_t), mai sigură decât NULL sau 0.

Sintaxă
int *p = nullptr;
if (p == nullptr) { ... }

#include <stddef.h>
nullptr_t   // tipul lui nullptr

Ce face nullptr

Un pointer nul nu indică spre niciun obiect. Până la C23 se scria NULL, o macro care, în funcție de sistem, este 0 sau ((void*)0). C23 adaugă cuvântul cheie nullptr, o constantă care este mereu pointer nul și are propriul tip, nullptr_t (declarat în <stddef.h>).

nullptr se poate atribui oricărui tip de pointer și se poate compara cu orice pointer, dar nu se poate pune într-un int: compilatorul refuză. Cu NULL definit ca 0, greșeala ar fi trecut neobservată. Diferența contează și în funcțiile cu argumente variabile și în _Generic, unde tipul argumentului este important.

Ca și înainte, a citi sau a scrie prin pointerul nul (*p când p == nullptr) este comportament nedefinit; de obicei programul se oprește. Verifică pointerul înainte să-l folosești.

Exemple

Exemplul 1: Funcție care returnează nullptr când nu găsește

cauta returnează adresa elevului sau nullptr. Apelantul verifică rezultatul înainte să-l folosească.

#include <stdio.h>
#include <string.h>

struct elev {
    const char *nume;
    int nota;
};

static struct elev *cauta(struct elev clasa[], int n, const char *nume) {
    for (int i = 0; i < n; i++) {
        if (strcmp(clasa[i].nume, nume) == 0) {
            return &clasa[i];
        }
    }
    return nullptr;
}

int main(void) {
    struct elev clasa[] = {{"Ana", 10}, {"Radu", 8}};
    const char *cautati[] = {"Radu", "Elena"};

    for (int i = 0; i < 2; i++) {
        struct elev *e = cauta(clasa, 2, cautati[i]);
        if (e == nullptr) {
            printf("%s nu este în catalog\n", cautati[i]);
        } else {
            printf("%s are nota %d\n", e->nume, e->nota);
        }
    }
    return 0;
}

Rezultat

Radu are nota 8
Elena nu este în catalog

Exemplul 2: Tipul nullptr_t și _Generic

_Generic alege o ramură după tipul expresiei. nullptr are tipul nullptr_t, diferit de int și de void *, deci poate fi recunoscut.

#include <stddef.h>
#include <stdio.h>

#define TIP(x) _Generic((x), \
    nullptr_t: "nullptr_t", \
    int: "int", \
    void *: "void *", \
    default: "alt tip")

int main(void) {
    void *adresa = nullptr;
    printf("nullptr  -> %s\n", TIP(nullptr));
    printf("0        -> %s\n", TIP(0));
    printf("adresa   -> %s\n", TIP(adresa));
    printf("sizeof(nullptr_t) = %zu\n", sizeof(nullptr_t));
    return 0;
}

Rezultat

nullptr  -> nullptr_t
0        -> int
adresa   -> void *
sizeof(nullptr_t) = 8

Exemplul 3: nullptr nu se poate pune într-un int

Compilatorul refuză să inițializeze un int cu nullptr. Cu NULL definit ca 0, aceeași linie s-ar fi compilat fără mesaj.

int main(void) {
    int *p = nullptr;      // corect
    int n = nullptr;       // greșit: n nu este pointer
    return n + (p != nullptr);
}

Mesajul compilatorului (programul nu se compilează)

exemplu.c:3:9: error: initializing 'int' with an expression of incompatible type 'nullptr_t'
    3 |     int n = nullptr;       // greșit: n nu este pointer
      |         ^   ~~~~~~~
1 error generated.

Greșeli frecvente

  • Crezi că nullptr face pointerul sigur. Nu face: dacă folosești *p fără să verifici, programul se oprește la fel ca înainte.
  • Eliberezi memoria cu free(p) și continui să folosești p. Pointerul încă are adresa veche (pointer „atârnat”). După free, scrie p = nullptr;.
  • Folosești nullptr în cod care trebuie compilat și cu standarde mai vechi. Înainte de C23 nu există; acolo rămâi la NULL.

Comenzi înrudite

  • Pointeri (& și *): O variabilă care păstrează adresa altei variabile; cu * citești sau modifici valoarea de la acea adresă.
  • malloc: Cere sistemului un bloc de memorie de mărimea dată, în timpul rulării, și returnează adresa lui sau NULL dacă nu reușește.
  • free: Eliberează un bloc alocat cu malloc, calloc sau realloc, ca memoria să poată fi refolosită.
  • void * (pointer generic): Un pointer care poate păstra adresa oricărui tip de date, dar care trebuie convertit la un tip concret înainte de folosire.
  • bool, true, false: Tipul pentru valori adevărat/fals. În C23, bool, true și false sunt cuvinte cheie, fără să mai incluzi stdbool.h.
  • typeof și auto: typeof dă tipul unei expresii, iar auto deduce tipul variabilei din valoarea inițială. Ambele sunt standard din C23.