union în C

Ca un struct, dar toate câmpurile folosesc aceeași zonă de memorie, așa că la un moment dat păstrează o singură valoare.

Sintaxă
union nume {
    tip1 camp1;
    tip2 camp2;
};   // toate câmpurile împart aceeași memorie

Ce face union

Un union se declară ca un struct, însă câmpurile nu stau unul după altul, ci se suprapun. Dimensiunea lui este cea a câmpului cel mai mare (plus eventual aliniere). Când scrii un câmp, îl suprascrii pe cel scris înainte.

Se folosește pentru a economisi memorie când o valoare poate fi de mai multe tipuri, dar niciodată de mai multe deodată. De obicei îl pui într-un struct, împreună cu un câmp (de multe ori un enum) care spune ce tip este activ acum: o „uniune etichetată”.

În C, citirea altui câmp decât cel scris ultima dată reinterpretează octeții (type punning); standardul permite asta, dar rezultatul depinde de reprezentarea tipurilor pe acel calculator.

Exemple

Exemplul 1: Câmpurile împart memoria

sizeof uniunii este cel al câmpului mare (double, 8 octeți), nu suma lor, ca la struct.

#include <stdio.h>

union valoare {
    int i;
    double d;
    char c;
};

struct toate {
    int i;
    double d;
    char c;
};

int main(void) {
    printf("sizeof(union valoare) = %zu\n", sizeof(union valoare));
    printf("sizeof(struct toate)  = %zu\n", sizeof(struct toate));
    union valoare v;
    v.i = 42;
    printf("v.i = %d\n", v.i);
    v.d = 3.75;   // suprascrie v.i
    printf("v.d = %.2f\n", v.d);
    return 0;
}

Rezultat

sizeof(union valoare) = 8
sizeof(struct toate)  = 24
v.i = 42
v.d = 3.75

Exemplul 2: Uniune etichetată: o valoare de mai multe tipuri

Câmpul tip spune care membru al uniunii este valid. Funcția de afișare citește doar membrul corect.

#include <stdio.h>

enum tip_camp { NUMAR, PRET, TEXT };

struct camp {
    enum tip_camp tip;
    union {
        int numar;
        double pret;
        const char *text;
    };   // uniune anonimă (C11): câmpurile se accesează direct
};

void afiseaza(struct camp c) {
    switch (c.tip) {
        case NUMAR: printf("număr: %d\n", c.numar); break;
        case PRET:  printf("preț: %.2f lei\n", c.pret); break;
        case TEXT:  printf("text: %s\n", c.text); break;
    }
}

int main(void) {
    struct camp campuri[] = {
        {.tip = TEXT, .text = "Ana"},
        {.tip = NUMAR, .numar = 7},
        {.tip = PRET, .pret = 19.9},
    };
    for (size_t i = 0; i < 3; i++) afiseaza(campuri[i]);
    return 0;
}

Rezultat

text: Ana
număr: 7
preț: 19.90 lei

Exemplul 3: Aceiași octeți văzuți în două feluri

Uniunea arată octeții unui uint32_t. Ordinea depinde de procesor: pe little-endian primul octet este cel mai puțin semnificativ (0x44).

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

union cuvant {
    uint32_t numar;
    uint8_t octeti[4];
};

int main(void) {
    union cuvant u = {.numar = 0x11223344};
    for (int i = 0; i < 4; i++) printf("octeti[%d] = 0x%02x\n", i, u.octeti[i]);
    return 0;
}

Rezultat

octeti[0] = 0x44
octeti[1] = 0x33
octeti[2] = 0x22
octeti[3] = 0x11

Greșeli frecvente

  • Crezi că un union păstrează toate câmpurile, ca un struct. Doar ultimul scris este valid; ține minte care, de obicei cu un câmp enum separat.
  • Folosești uniuni pentru a converti între tipuri când ai nevoie de o conversie de valoare. (int)3.7 dă 3; citirea octeților unui double ca int printr-o uniune dă cu totul altceva.
  • Inițializezi o uniune cu mai multe valori: union valoare v = {1, 2.5};. Se inițializează doar primul câmp; pentru altul folosește {.d = 2.5}.

Comenzi înrudite

  • struct: Grupează mai multe valori de tipuri diferite într-un singur tip nou, de exemplu numele, vârsta și orașul unei persoane.
  • enum: Definește un set de constante întregi cu nume, de exemplu zilele săptămânii sau stările unei comenzi.
  • sizeof: Operatorul care spune câți octeți ocupă un tip sau o variabilă; calculat de compilator, rezultatul are tipul size_t.
  • switch: Alege una din mai multe ramuri după valoarea unui întreg, cu etichete case; fără break, execuția continuă în ramura următoare.
  • stdint.h și limits.h: Tipuri întregi cu dimensiune exactă (int32_t, uint8_t) și constantele cu valorile minime și maxime ale tipurilor (INT_MAX).