Pointer la pointer (**) în C

Pointeri C89

Un pointer care păstrează adresa altui pointer: util când o funcție trebuie să schimbe un pointer sau pentru tablouri de șiruri.

Sintaxă
tip **pp;          // pointer spre un pointer la tip
pp = &p;
**pp = valoare;    // valoarea finală
int main(int argc, char **argv)

Ce face Pointer la pointer (**)

Un pointer este tot o variabilă, deci are și el o adresă. int **pp = &p; păstrează adresa pointerului p. *pp este p, iar **pp este valoarea spre care arată p.

Cel mai des îl folosești în două situații: când o funcție trebuie să modifice un pointer al apelantului (de exemplu să-i aloce memorie) și pentru tablouri de șiruri, cum este char **argv din main sau un const char *nume[] trimis unei funcții.

Se poate merge și mai departe (***), dar codul devine greu de citit; de obicei un struct sau un typedef ajută mai mult.

Exemple

Exemplul 1: Două niveluri de adrese

Prin pp poți ajunge la x (cu **pp) sau poți schimba spre ce arată p (cu *pp).

#include <stdio.h>

int main(void) {
    int x = 10, y = 20;
    int *p = &x;
    int **pp = &p;
    printf("**pp = %d\n", **pp);
    *pp = &y;            // acum p arată spre y
    printf("*p = %d\n", *p);
    **pp = 25;           // modifică y
    printf("y = %d\n", y);
    return 0;
}

Rezultat

**pp = 10
*p = 20
y = 25

Exemplul 2: O funcție care alocă memorie pentru apelant

Funcția primește adresa pointerului din main și scrie în el adresa memoriei alocate. Cu un simplu int * ar fi schimbat doar copia locală.

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

int creeaza_note(int **rezultat, size_t n) {
    int *v = malloc(n * sizeof *v);
    if (v == NULL) return 0;
    for (size_t i = 0; i < n; i++) v[i] = 10 - (int)i;
    *rezultat = v;
    return 1;
}

int main(void) {
    int *note = NULL;
    if (creeaza_note(&note, 4)) {
        for (size_t i = 0; i < 4; i++) printf("%d ", note[i]);
        printf("\n");
        free(note);
    }
    return 0;
}

Rezultat

10 9 8 7

Exemplul 3: Un tablou de șiruri trimis unei funcții

Parametrul const char **orase primește tabloul de pointeri. Fiecare orase[i] este un const char *.

#include <stdio.h>

void afiseaza(const char **orase, size_t n) {
    for (size_t i = 0; i < n; i++) {
        printf("%zu. %s\n", i + 1, orase[i]);
    }
}

int main(void) {
    const char *orase[] = {"Iași", "Cluj-Napoca", "Timișoara"};
    afiseaza(orase, sizeof orase / sizeof orase[0]);
    return 0;
}

Rezultat

1. Iași
2. Cluj-Napoca
3. Timișoara

Greșeli frecvente

  • Trimiți unei funcții care alocă memorie un int * în loc de int **. Alocarea se face, dar pointerul din apelant rămâne neschimbat, iar memoria se pierde.
  • Crezi că int m[3][4] este un int **. Matricea este un bloc continuu de int; un int ** este un tablou de pointeri. Nu le poți schimba între ele.
  • Uiți un nivel de *: *pp = 5 pune 5 în pointer (ca adresă), nu în valoarea finală. Compilatorul avertizează, iar cu -Werror codul nu se compilează.

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.
  • Tablouri bidimensionale (matrice): Un tablou de tablouri: date așezate pe rânduri și coloane, accesate cu doi indici, m[rând][coloană].
  • main: Funcția cu care începe orice program C: sistemul o apelează la pornire, iar valoarea returnată spune dacă programul a reușit.
  • Șiruri de caractere (char[]): În C, un șir de caractere este un tablou de char terminat cu caracterul nul ‘\0’, care marchează sfârșitul textului.