void * (pointer generic) în C

Pointeri C89

Un pointer care poate păstra adresa oricărui tip de date, dar care trebuie convertit la un tip concret înainte de folosire.

Sintaxă
void *p = &orice;
tip *t = p;            // conversie implicită înapoi
*(tip *)p              // trebuie convertit înainte de dereferențiere

Ce face void * (pointer generic)

void * este pointerul „fără tip”: poate primi adresa oricărei variabile, fără conversie explicită. Funcțiile generice din biblioteca standard îl folosesc: malloc returnează void *, iar memcpy, memset, qsort și bsearch primesc void *.

Nu poți dereferenția direct un void *, pentru că nu se știe câți octeți să citești. Îl convertești întâi la tipul real: *(int *)p. În C (spre deosebire de C++), conversia din void * în alt pointer este implicită, deci int *v = malloc(...) nu are nevoie de cast.

Standardul nu permite aritmetică pe void *; pentru a avansa octet cu octet, convertește la unsigned char *.

Exemple

Exemplul 1: Același pointer, tipuri diferite

Funcția primește un void * și un cod care spune ce tip este, apoi face conversia potrivită.

#include <stdio.h>

enum tip { INT, DOUBLE, TEXT };

void afiseaza(const void *p, enum tip t) {
    switch (t) {
        case INT:    printf("int: %d\n", *(const int *)p); break;
        case DOUBLE: printf("double: %.2f\n", *(const double *)p); break;
        case TEXT:   printf("text: %s\n", (const char *)p); break;
    }
}

int main(void) {
    int varsta = 34;
    double pret = 49.99;
    afiseaza(&varsta, INT);
    afiseaza(&pret, DOUBLE);
    afiseaza("Iași", TEXT);
    return 0;
}

Rezultat

int: 34
double: 49.99
text: Iași

Exemplul 2: Octeții unei variabile

Convertind la const unsigned char * poți citi fiecare octet. Pe procesoarele little-endian (x86, ARM din Mac-uri și telefoane), octetul cel mai puțin semnificativ vine primul.

#include <stdio.h>
#include <stdint.h>

void octeti(const void *p, size_t n) {
    const unsigned char *b = p;
    for (size_t i = 0; i < n; i++) printf("%02x ", b[i]);
    printf("\n");
}

int main(void) {
    uint32_t cod = 0x11223344;
    octeti(&cod, sizeof cod);
    return 0;
}

Rezultat

44 33 22 11

Exemplul 3: O funcție generică de interschimbare

Funcția nu știe tipul, doar dimensiunea. Copiază octeții cu memcpy printr-un tampon local, deci merge pentru int, double sau struct.

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

void schimba(void *a, void *b, size_t dim) {
    unsigned char temp[64];
    if (dim > sizeof temp) return;
    memcpy(temp, a, dim);
    memcpy(a, b, dim);
    memcpy(b, temp, dim);
}

int main(void) {
    double x = 1.5, y = 9.25;
    schimba(&x, &y, sizeof x);
    printf("x = %.2f, y = %.2f\n", x, y);
    return 0;
}

Rezultat

x = 9.25, y = 1.50

Greșeli frecvente

  • Dereferențiezi direct un void * (*p). Compilatorul refuză; convertește întâi la tipul corect.
  • Convertești la un tip greșit: un double citit prin (int *) dă valori fără sens și încalcă regulile de aliasing. Ține evidența tipului real.
  • Scrii p + 1 pe un void *. Clang și GCC îl acceptă ca extensie (pas de 1 octet), dar -Wpedantic avertizează; folosește unsigned char *.

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.
  • memcpy: Copiază n octeți dintr-o zonă de memorie în alta; zonele nu au voie să se suprapună.
  • qsort: Sortează un tablou de orice tip, folosind o funcție de comparare scrisă de tine.
  • Aritmetica pointerilor: Adunarea și scăderea pe pointeri se fac în elemente, nu în octeți: p + 1 arată spre următorul element din tablou.