C Unions
Uma struct guarda todos os seus campos ao mesmo tempo: nome e idade e nota. A union é parecida no jeito de escrever, mas guarda um campo de cada vez: todos eles moram no mesmo pedaço da memória, e gravar um apaga o outro. Parece estranho, mas resolve um problema comum: uma informação que pode ser de tipos diferentes, conforme o caso. Na lojinha da Ana, um pagamento em dinheiro tem o valor recebido (para calcular o troco), um pagamento no cartão tem o número de parcelas, e um Pix tem a chave. Nunca os três juntos. Nesta aula você vai ver a union por dentro, aprender a usá-la com segurança (junto com um enum) e espiar os bytes de um número.
- A diferença entre struct e union, vista no
sizeofe nos endereços. - Por que só o último membro gravado vale, e o que sai quando você lê outro.
- Como criar uma union já com valor:
{7},{.real = 2.5},{0}. - A union marcada: uma struct com um enum que diz qual membro da union está valendo.
- A union anônima do C11, que dispensa um nome no meio do caminho.
- Como usar uma union para ver os bytes de um
floate de um inteiro.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. A aula usa structs, enums e o operador -> (aula Structs e Ponteiros).
A ideia: uma vaga de garagem
O prédio da Ana tem um depósito com várias prateleiras, uma para cada coisa: bicicleta, caixa de ferramentas, cadeira de praia. Tudo cabe ao mesmo tempo, e o depósito precisa ter o tamanho de todas as prateleiras somadas. Isso é a struct.
Já a vaga de garagem recebe um carro ou uma moto ou uma bicicleta. Uma coisa por vez. Ela precisa ter só o tamanho da maior delas, o carro. Se a Ana estaciona a moto, o carro não está mais lá. E se alguém chega procurando o carro numa vaga onde está a moto, vai encontrar… uma moto. Isso é a union.
| struct (o depósito) | union (a vaga) | |
|---|---|---|
| Quantos membros guardam valor ao mesmo tempo | Todos | Um só: o último gravado |
| Tamanho | A soma de todos (mais o enchimento) | O do maior membro (mais o enchimento) |
| Endereço de cada membro | Um diferente para cada | O mesmo para todos |
| Para que serve | Juntar informações que andam juntas | Uma informação que pode ter tipos diferentes |
O nome vem do inglês union, "união": vários tipos unidos no mesmo lugar.
1. struct × union: o tamanho
A melhor forma de entender a union é colocá-la ao lado de uma struct com os mesmos membros. A escrita é idêntica; só muda a palavra do começo:
#include <stdio.h>
struct Caixa {
int inteiro;
double real;
char letra;
};
union Gaveta {
int inteiro;
double real;
char letra;
};
int main(void) {
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(double) = %zu\n", sizeof(double));
printf("sizeof(char) = %zu\n", sizeof(char));
printf("sizeof(struct Caixa) = %zu\n", sizeof(struct Caixa));
printf("sizeof(union Gaveta) = %zu\n", sizeof(union Gaveta));
return 0;
}
- union Gaveta { … };
- Declara um tipo novo, como a struct:
union+ etiqueta + membros entre chaves +;. Cada membro é tipo + nome. - int inteiro; double real; char letra;
- Os membros (o nome usado na union, já que "campo" lembra algo que existe ao lado dos outros). A union pode guardar um
intou umdoubleou umchar. - sizeof(union Gaveta)
- O tamanho da union inteira, em bytes (aula sizeof).
$ gcc -std=c23 -Wall -Wextra tamanho.c -o tamanho $ ./tamanho sizeof(int) = 4 sizeof(double) = 8 sizeof(char) = 1 sizeof(struct Caixa) = 24 sizeof(union Gaveta) = 8
A union tem 8 bytes: o tamanho do maior membro, o double. Nela cabe um double inteiro, ou um int (usando 4 dos 8 bytes), ou um char (usando 1). A struct tem 24: os 4 + 8 + 1 = 13 bytes dos campos e mais 11 de enchimento (padding), que o compilador põe para cada double começar num endereço múltiplo de 8 (veja Structs). O editor do guia mostra os mesmos números.
2. Todos os membros no mesmo endereço
Para ver que os membros da union moram no mesmo lugar, imprima o endereço de cada um com %p (aula Memória e Endereços):
#include <stdio.h>
union Valor {
int inteiro;
double real;
char letra;
};
struct Ficha {
int inteiro;
double real;
char letra;
};
int main(void) {
union Valor v;
struct Ficha f;
printf("union: %p %p %p\n", (void *)&v.inteiro, (void *)&v.real, (void *)&v.letra);
printf("struct: %p %p %p\n", (void *)&f.inteiro, (void *)&f.real, (void *)&f.letra);
return 0;
}
$ gcc -std=c23 -Wall -Wextra endereco.c -o endereco $ ./endereco union: 0x7fff99a00558 0x7fff99a00558 0x7fff99a00558 « o mesmo endereço, três vezes struct: 0x7fff99a00560 0x7fff99a00568 0x7fff99a00570 « um endereço para cada campo
Na union, os três membros começam no mesmo byte. Na struct, cada campo tem o seu lugar, um depois do outro. Os números mudam a cada execução e no editor do guia saem bem menores (0x7ffff0), porque lá a memória é organizada de outro jeito; o que importa é a comparação entre eles.
3. Só o último gravado vale
Se todos os membros ocupam o mesmo lugar, gravar um escreve por cima dos outros:
#include <stdio.h>
union Valor {
int inteiro;
double real;
};
int main(void) {
union Valor v;
v.inteiro = 42;
printf("inteiro = %d\n", v.inteiro);
v.real = 3.75;
printf("real = %.2f\n", v.real);
printf("inteiro = %d\n", v.inteiro);
return 0;
}
- union Valor v;
- Cria uma variável do tipo
union Valor(as duas palavras, como emstruct Aluno). Por enquanto, 8 bytes com lixo. - v.inteiro = 42;
- O acesso é com o ponto, igual à struct. Grava 42 nos 4 primeiros bytes.
- v.real = 3.75;
- Grava o
double3.75 nos 8 bytes, por cima do 42. - printf(…, v.inteiro)
- Lê os 4 primeiros bytes como se fossem um
int. Mas eles agora são um pedaço do 3.75.
$ gcc -std=c23 -Wall -Wextra primeira.c -o primeira $ ./primeira inteiro = 42 real = 3.75 inteiro = 0 « o 42 sumiu: ali estão bytes do 3.75
O 42 não existe mais. O 0 que apareceu não é "zerado": são os 4 primeiros bytes do double 3.75, que por acaso são todos zero. Com outro número, sairia outra coisa. Repare que nem o gcc nem o Clang avisam: a union não guarda qual membro foi gravado por último. Lembrar disso é trabalho seu, e a seção 5 mostra como fazer o programa lembrar.
Experimente: rode e confira o inteiro = 0. Depois troque o 3.75 por 3.1 e veja o "inteiro" virar um número negativo enorme, sem sentido. Por último, mude a ordem: grave o real primeiro e o inteiro depois. Quem sobrevive agora?
#include <stdio.h>
union Valor {
int inteiro;
double real;
};
int main(void) {
union Valor v;
v.inteiro = 42;
printf("inteiro = %d\n", v.inteiro);
v.real = 3.75;
printf("real = %.2f\n", v.real);
printf("inteiro = %d\n", v.inteiro);
printf("sizeof(v) = %zu\n", sizeof v);
return 0;
}
4. Criar a union já com valor
Como na struct, você pode dar valor na criação, com chaves. Mas só um valor, já que só um membro vale por vez:
#include <stdio.h>
union Valor {
int inteiro;
double real;
};
int main(void) {
union Valor a = {7};
union Valor b = {.real = 2.5};
union Valor c = {0};
union Valor d = {2.5};
printf("a.inteiro = %d\n", a.inteiro);
printf("b.real = %.1f\n", b.real);
printf("c.inteiro = %d, c.real = %.1f\n", c.inteiro, c.real);
printf("d.inteiro = %d\n", d.inteiro);
return 0;
}
$ gcc -std=c23 -Wall -Wextra iniciar.c -o iniciar $ ./iniciar a.inteiro = 7 b.real = 2.5 c.inteiro = 0, c.real = 0.0 d.inteiro = 2 « o 2.5 foi para o inteiro e perdeu o .5
| Escrita | O que faz |
|---|---|
{7} | Sem nome, o valor vai para o primeiro membro da lista (inteiro). |
{.real = 2.5} | Com o designador C99, você escolhe o membro. É a forma mais clara. |
{0} e {} | Tudo zerado ({} a partir do C23). |
{2.5} | A armadilha: sem designador, o 2.5 vai para o primeiro membro, que é int, e vira 2. O gcc não avisa nada. O Clang do editor avisa: implicit conversion from 'double' to 'int' changes value from 2.5 to 2 [-Wliteral-conversion]. |
Regra prática: numa union, use sempre o designador (.membro = valor). Assim ninguém precisa lembrar qual membro é o primeiro.
5. A union marcada: o enum diz qual membro vale
Sozinha, a union é perigosa: o programa não sabe qual membro está valendo. Por isso, quase sempre ela anda dentro de uma struct, ao lado de um enum que serve de etiqueta: "aqui dentro está um cartão". Esse par se chama union marcada (tagged union). É o uso mais importante da union. Veja o caixa da lojinha:
#include <stdio.h>
enum Forma { DINHEIRO, CARTAO, PIX };
struct Pagamento {
enum Forma forma;
double valor;
union {
double recebido; /* DINHEIRO: quanto o cliente deu */
int parcelas; /* CARTAO: em quantas vezes */
char chave[40]; /* PIX: a chave usada */
} info;
};
void mostrar(const struct Pagamento *p) {
printf("R$ %6.2f ", p->valor);
switch (p->forma) {
case DINHEIRO:
printf("em dinheiro, troco de R$ %.2f\n", p->info.recebido - p->valor);
break;
case CARTAO:
printf("no cartão, em %dx de R$ %.2f\n", p->info.parcelas, p->valor / p->info.parcelas);
break;
case PIX:
printf("por Pix, chave %s\n", p->info.chave);
break;
}
}
int main(void) {
struct Pagamento vendas[3] = {
{.forma = DINHEIRO, .valor = 37.50, .info.recebido = 50.00},
{.forma = CARTAO, .valor = 120.00, .info.parcelas = 3},
{.forma = PIX, .valor = 15.90, .info.chave = "ana@lojinha.com"},
};
for (int i = 0; i < 3; i++) {
mostrar(&vendas[i]);
}
printf("sizeof(struct Pagamento) = %zu\n", sizeof(struct Pagamento));
return 0;
}
- enum Forma { DINHEIRO, CARTAO, PIX };
- As três formas de pagamento (aula Enums). Cada uma diz qual membro da union está valendo.
- enum Forma forma;
- A etiqueta (a "marca"). Ela fica fora da union, para nunca ser apagada.
- double valor;
- O que todo pagamento tem, qualquer que seja a forma, também fica fora da union.
- union { … } info;
- Uma union sem etiqueta, declarada ali mesmo, como membro
infoda struct. Só um dos três vale em cada pagamento. - .info.recebido = 50.00
- Designador em dois níveis: o membro
infoda struct, e dentro dele orecebido. - switch (p->forma)
- Antes de ler a union, olha a etiqueta. Cada
caselê só o membro que combina com ela. Comoformaé um enum, o-Wallavisa se faltar uma forma noswitch. - p->info.parcelas
- A seta entra na struct pelo ponteiro, e o ponto entra na union.
$ gcc -std=c23 -Wall -Wextra pagamento.c -o pagamento $ ./pagamento R$ 37.50 em dinheiro, troco de R$ 12.50 R$ 120.00 no cartão, em 3x de R$ 40.00 R$ 15.90 por Pix, chave ana@lojinha.com sizeof(struct Pagamento) = 56
56 bytes: 4 do enum, 4 de enchimento, 8 do valor e 40 da union (o tamanho da chave, o maior membro). Com os três campos separados numa struct (recebido, parcelas e chave lado a lado), o mesmo pagamento ocuparia 72 bytes. A economia é pequena aqui, mas o ganho maior é outro: o tipo struct Pagamento diz, com clareza, que cada pagamento tem uma dessas informações, e nunca as três.
Sempre que gravar um membro da union, atualize a etiqueta na mesma hora; sempre que for ler, olhe a etiqueta primeiro. Uma forma de não esquecer é criar funções que fazem as duas coisas juntas, como struct Pagamento pagar_pix(double valor, const char *chave).
6. C11 Union anônima: sem o nome do meio
Escrever p.info.parcelas toda hora é comprido. A partir do C11, a union dentro da struct pode não ter nome nenhum. Os membros dela passam a ser usados como se fossem da struct:
#include <stdio.h>
enum Forma { DINHEIRO, CARTAO, PIX };
struct Pagamento {
enum Forma forma;
double valor;
union {
double recebido;
int parcelas;
char chave[40];
};
};
int main(void) {
struct Pagamento p = {.forma = CARTAO, .valor = 120.00, .parcelas = 4};
printf("%dx de R$ %.2f\n", p.parcelas, p.valor / p.parcelas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra anonima.c -o anonima $ ./anonima 4x de R$ 30.00
Repare: union { … }; sem nome antes do ;, e o acesso é p.parcelas, direto. Por dentro, nada mudou: recebido, parcelas e chave continuam dividindo o mesmo lugar. A missão final usa essa forma.
7. Espiar os bytes de um número
A union tem um segundo uso, mais técnico: gravar por um membro e ler por outro de propósito, para ver como o computador guarda um valor. Em C, isso é permitido: os bytes são relidos como o outro tipo. (Em C++, não é; lá se usa memcpy.) Este programa mostra os 32 bits de um float:
#include <stdio.h>
#include <stdint.h>
union Numero {
float f;
uint32_t bits;
};
int main(void) {
union Numero n;
n.f = 1.0f;
printf("%.1f fica guardado como 0x%08X\n", n.f, n.bits);
n.f = -1.0f;
printf("%.1f fica guardado como 0x%08X\n", n.f, n.bits);
n.f = 0.1f;
printf("%.1f fica guardado como 0x%08X\n", n.f, n.bits);
return 0;
}
$ gcc -std=c23 -Wall -Wextra bits.c -o bits $ ./bits 1.0 fica guardado como 0x3F800000 -1.0 fica guardado como 0xBF800000 0.1 fica guardado como 0x3DCCCCCD
O uint32_t é um inteiro sem sinal de exatamente 32 bits, do tamanho do float (aula stdint.h). O %08X mostra o número em hexadecimal com 8 dígitos. Repare que 1.0 e -1.0 diferem só no primeiro dígito: o bit mais alto do float é o sinal. E o 0.1 termina em CCCCCD, uma dízima arredondada: é por isso que 0.1 não é exato no computador (aula float e double).
O mesmo truque mostra a ordem dos bytes de um inteiro:
#include <stdio.h>
#include <stdint.h>
union Pedacos {
uint32_t numero;
unsigned char byte[4];
};
int main(void) {
union Pedacos p;
p.numero = 0x12345678;
for (int i = 0; i < 4; i++) {
printf("byte[%d] = %02x\n", i, p.byte[i]);
}
return 0;
}
$ gcc -std=c23 -Wall -Wextra bytes.c -o bytes $ ./bytes byte[0] = 78 byte[1] = 56 byte[2] = 34 byte[3] = 12
O número 0x12345678 ficou guardado "de trás para frente": o byte 78, o menos importante, vem primeiro na memória. Esse jeito se chama little-endian, e é o dos processadores Intel, AMD e da maioria dos ARM. O editor do guia (WebAssembly) também é little-endian e mostra o mesmo. A aula Arquivos Binários mostra por que isso importa quando o número vai para um arquivo.
struct, union ou variáveis separadas: qual usar
| Situação | Use | Exemplo |
|---|---|---|
| Várias informações que existem ao mesmo tempo | struct | Aluno com nome, idade e nota. |
| Uma informação que pode ser de tipos diferentes, um por vez | union marcada (struct + enum + union) | Pagamento em dinheiro, cartão ou Pix; uma célula de planilha com número ou texto. |
| Ver os bytes de um valor | union de dois tipos do mesmo tamanho | float + uint32_t. |
| Poucos casos e memória sobrando | struct com todos os campos | Mais simples de entender; gasta um pouco mais. |
| Union "solta", sem etiqueta | Evite | Ninguém sabe qual membro vale: convite para ler lixo. |
Economizar memória era o grande motivo da union nos computadores antigos. Hoje, para um programa comum, alguns bytes não fazem diferença, e a union aparece mais pela clareza (a union marcada) e em código de baixo nível: protocolos de rede, formatos de arquivo, programas para microcontroladores com pouca memória.
Quando dá errado
struct Pagamento p = {.forma = PIX, .valor = 15.90, .info.chave = "ana@lojinha.com"};
printf("Parcelas: %d\n", p.info.parcelas);
printf("Recebido: %g\n", p.info.recebido);
$ gcc -std=c23 -Wall -Wextra errado.c -o errado $ ./errado Parcelas: 1080127073 Recebido: 6.3234e+199
O que aconteceu: o pagamento é um Pix, então o que está na union é o texto "ana@lojinha.com". O programa leu esses bytes como int e como double. O 1080127073 são as letras a, n, a, @ lidas como número (em hexadecimal, 0x40616e61: os códigos de @, a, n e a, a palavra de trás para frente, por causa da ordem dos bytes da seção 7). Nenhum aviso, nenhum erro: só dados errados, e o editor do guia mostra os mesmos números.
Como resolver: antes de ler a union, olhe a etiqueta (if (p.forma == CARTAO) ou um switch), como no pagamento.c.
$ gcc -std=c23 -Wall -Wextra semunion.c -o semunion
semunion.c: In function ‘main’:
semunion.c:9:5: error: unknown type name ‘Valor’; use ‘union’ keyword to refer to the type
9 | Valor v;
| ^~~~~
| union
semunion.c:10:6: error: request for member ‘inteiro’ in something not a structure or union
10 | v.inteiro = 3;
| ^
«…»
O que aconteceu: como na struct e no enum, o nome do tipo são as duas palavras: union Valor. O segundo erro é consequência do primeiro: request for member ‘inteiro’ in something not a structure or union = "pediu o membro inteiro de algo que não é struct nem union". O Clang do editor diz must use 'union' tag to refer to type 'Valor'.
Como resolver: escreva union Valor v;, ou crie um apelido com typedef: typedef union { … } Valor;.
$ gcc -std=c23 -Wall -Wextra membro.c -o membro
membro.c: In function ‘main’:
membro.c:9:23: error: ‘union Valor’ has no member named ‘texto’
9 | union Valor v = {.texto = "oi"};
| ^~~~~
membro.c:9:31: error: initialization of ‘int’ from ‘char *’ makes integer from pointer without a cast [-Wint-conversion]
9 | union Valor v = {.texto = "oi"};
| ^~~~
membro.c:9:31: note: (near initialization for ‘v.inteiro’)
O que aconteceu: a union só tem inteiro e real. O designador .texto não existe, e o gcc ainda tentou pôr o "oi" no primeiro membro, o que gerou o segundo erro. O Clang do editor diz field designator 'texto' does not refer to any field in type 'union Valor'.
Como resolver: confira o nome do membro na declaração, ou acrescente o membro que falta (char texto[20];).
$ gcc -std=c23 -Wall -Wextra duasvezes.c -o duasvezes
duasvezes.c: In function ‘main’:
duasvezes.c:9:44: warning: initialized field overwritten [-Woverride-init]
9 | union Valor v = {.inteiro = 1, .real = 2.0};
| ^~~
duasvezes.c:9:44: note: (near initialization for ‘v.real’)
$ ./duasvezes
2.0
O que aconteceu: "campo inicializado foi sobrescrito". Numa union só cabe um valor; o .real = 2.0 apagou o .inteiro = 1. O Clang do editor diz initializer overrides prior initialization of this subobject.
Como resolver: dê valor a um membro só. Se você precisa dos dois ao mesmo tempo, o tipo certo é uma struct.
O que aconteceu: o programa mudou p.parcelas = 2; mas esqueceu o p.forma = CARTAO;. Para o resto do programa, o pagamento continua sendo em dinheiro, e o "valor recebido" agora é o lixo da seção 3. O compilador não tem como perceber.
Como resolver: grave o membro e a etiqueta sempre juntos, de preferência numa função só (a dica da seção 5).
Referência rápida
| Escrita | Significado |
|---|---|
union Valor { int inteiro; double real; }; | Declara o tipo: um int ou um double no mesmo lugar. |
union Valor v; | Cria uma variável (com lixo, se for local). |
v.real = 2.5; | Grava num membro e apaga o que havia nos outros. |
sizeof(union Valor) | O tamanho do maior membro, mais o enchimento. |
union Valor v = {.real = 2.5}; | Cria com valor num membro escolhido (C99). |
union Valor v = {7}; | Sem designador: o valor vai para o primeiro membro. |
struct { enum Forma forma; union { … } info; }; | Union marcada: a etiqueta diz qual membro vale. |
struct { …; union { int a; double b; }; }; | Union anônima (C11): acesso direto, s.a. |
p->info.parcelas | Membro da union dentro de uma struct, por ponteiro. |
union { float f; uint32_t bits; } | Grava por um tipo, lê os bytes pelo outro. |
Exercícios
1. O tamanho
Quantos bytes tem union { char c; int i; double d; } no gcc do laboratório? (char = 1, int = 4, double = 8)
2. Escolher o membro
Complete para criar a union Valor v com o membro real valendo 9.5:
3. A palavra que falta
Complete a declaração para que os três membros dividam o mesmo lugar na memória:
4. Depois de v.inteiro = 10; v.real = 1.5;, o que vale v.inteiro?
5. Para que serve o enum numa union marcada?
6. Com union Valor { int inteiro; double real; };, o que acontece em union Valor d = {2.5}; no gcc?
Missão final: a cesta da feira
Missão: na feira, a banana e o queijo são vendidos por quilo (o item guarda kg, um double) e o ovo, por unidade (guarda unidades, um int). Os dois dividem uma union anônima, e o campo tipo diz qual vale. A função total está usando kg para todo mundo, e o ovo saiu de graça. Corrija: olhe o tipo e use o membro certo. A saída deve ser:
Banana: 1.500 kg = R$ 7.35 Ovo: 12 un = R$ 9.60 Queijo: 0.250 kg = R$ 13.00 Total: R$ 29.95
#include <stdio.h>
enum Venda { POR_KG, POR_UNIDADE };
struct Item {
char nome[20];
enum Venda tipo;
double preco; /* preço do kg ou de uma unidade */
union {
double kg; /* vale quando tipo == POR_KG */
int unidades; /* vale quando tipo == POR_UNIDADE */
};
};
double total(const struct Item *it) {
return it->preco * it->kg;
}
void mostrar(const struct Item *it) {
if (it->tipo == POR_KG) {
printf("%s: %.3f kg", it->nome, it->kg);
} else {
printf("%s: %d un", it->nome, it->unidades);
}
printf(" = R$ %.2f\n", total(it));
}
int main(void) {
struct Item cesta[] = {
{"Banana", POR_KG, 4.90, .kg = 1.5},
{"Ovo", POR_UNIDADE, 0.80, .unidades = 12},
{"Queijo", POR_KG, 52.00, .kg = 0.250},
};
double soma = 0.0;
for (int i = 0; i < 3; i++) {
mostrar(&cesta[i]);
soma += total(&cesta[i]);
}
printf("Total: R$ %.2f\n", soma);
return 0;
}
#include <stdio.h>
enum Venda { POR_KG, POR_UNIDADE };
struct Item {
char nome[20];
enum Venda tipo;
double preco; /* preço do kg ou de uma unidade */
union {
double kg; /* vale quando tipo == POR_KG */
int unidades; /* vale quando tipo == POR_UNIDADE */
};
};
double total(const struct Item *it) {
if (it->tipo == POR_KG) {
return it->preco * it->kg;
}
return it->preco * it->unidades;
}
void mostrar(const struct Item *it) {
if (it->tipo == POR_KG) {
printf("%s: %.3f kg", it->nome, it->kg);
} else {
printf("%s: %d un", it->nome, it->unidades);
}
printf(" = R$ %.2f\n", total(it));
}
int main(void) {
struct Item cesta[] = {
{"Banana", POR_KG, 4.90, .kg = 1.5},
{"Ovo", POR_UNIDADE, 0.80, .unidades = 12},
{"Queijo", POR_KG, 52.00, .kg = 0.250},
};
double soma = 0.0;
for (int i = 0; i < 3; i++) {
mostrar(&cesta[i]);
soma += total(&cesta[i]);
}
printf("Total: R$ %.2f\n", soma);
return 0;
}
Rode primeiro sem mudar nada: o ovo aparece com R$ 0.00. É a seção 3 de novo: os bytes do int 12, lidos como double, dão um número tão pequeno que arredonda para zero.
Resumo
- Uma union se escreve como uma struct, mas todos os membros ocupam o mesmo lugar: o tamanho é o do maior membro.
- Só o último membro gravado vale. Ler outro dá os mesmos bytes interpretados como outro tipo, sem aviso nenhum.
- Crie com designador:
union Valor v = {.real = 2.5};. Sem ele, o valor vai para o primeiro membro. - O jeito seguro é a union marcada: uma struct com um
enum(a etiqueta) e a union. Grave os dois juntos; leia a etiqueta antes da union. - A union anônima C11 deixa escrever
p.parcelasem vez dep.info.parcelas. - Em C, gravar por um membro e ler por outro de propósito mostra os bytes de um valor:
1.0fé0x3F800000, e os inteiros ficam em little-endian.