C Tamanho dos Tipos (sizeof)
Cada variável ocupa um pedaço da memória do computador, e o tamanho desse pedaço depende do tipo: um char cabe em 1 byte, um double precisa de 8. Em C, você pode perguntar esse tamanho ao compilador com o operador sizeof ("tamanho de"). Parece curiosidade, mas é uma ferramenta do dia a dia: com ele você descobre quantos elementos tem um array, quanta memória pedir com malloc e por que um programa se comporta diferente em computadores diferentes. Nesta aula, você vai ver isso acontecer de verdade: o editor do guia e o gcc do Linux dão respostas diferentes para a mesma pergunta.
- O que é um byte e por que cada tipo tem um tamanho.
- As duas formas de usar o operador:
sizeof(int)esizeof x. - O tipo
size_te o formato%zu, que mostra o resultado. - Quanto ocupa cada tipo no gcc do Linux (64 bits) e no editor do guia (WebAssembly, 32 bits), e por que são diferentes.
- O tamanho de um array e a conta que descobre quantos elementos ele tem.
- Os erros comuns:
%dno lugar de%zu,sizeof intsem parênteses e osizeofde um array dentro de uma função.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook, um Linux de 64 bits), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. Quando o editor do guia dá um resultado diferente, a aula mostra os dois.
A ideia: caixas de tamanhos diferentes
A memória do computador é como uma fileira enorme de gavetinhas, todas do mesmo tamanho. Cada gavetinha é um byte, e guarda um número de 0 a 255 (8 bits, 8 "zeros e uns").
Um número pequeno, como a letra 'A' (código 65), cabe numa gavetinha. Um número maior precisa de várias gavetinhas lado a lado, como uma caixa de mudança que ocupa várias prateleiras. Cada tipo do C ocupa um número fixo de gavetinhas:
| Tipo | Bytes (gcc, Linux 64 bits) | O que cabe |
|---|---|---|
char | 1 | uma letra, ou um número de -128 a 127 |
int | 4 | números até cerca de 2 bilhões (para mais ou para menos) |
double | 8 | números com vírgula, com uns 15 algarismos de precisão |
Mais bytes = números maiores ou mais precisos, mas também mais memória gasta. O sizeof responde: "quantas gavetinhas este tipo (ou esta variável) ocupa?".
1. sizeof de um tipo
#include <stdio.h>
int main(void) {
printf("Um int ocupa %zu bytes.\n", sizeof(int));
printf("Um double ocupa %zu bytes.\n", sizeof(double));
printf("Um char ocupa %zu byte.\n", sizeof(char));
return 0;
}
- sizeof
- Um operador da linguagem (como o
+), e não uma função: não precisa de#include. Ele devolve o tamanho em bytes. - (int)
- Para perguntar o tamanho de um tipo, o nome do tipo vai entre parênteses, obrigatoriamente.
- %zu
- O formato do
printfpara o resultado desizeof. Ozavisa que o valor é umsize_t(seção 3) e oudiz que ele não tem sinal (unsigned). Veja Formatos do printf.
$ gcc -std=c23 -Wall -Wextra tamanho.c -o tamanho $ ./tamanho Um int ocupa 4 bytes. Um double ocupa 8 bytes. Um char ocupa 1 byte.
O sizeof(char) é sempre 1, em qualquer computador: a linguagem define o byte como "o tamanho de um char". Todo o resto pode variar, como a próxima seção mostra.
2. Todos os tipos, em dois computadores
Este programa pergunta o tamanho dos tipos básicos. O int * é um ponteiro (um endereço de memória, aula Ponteiros) e o size_t é o tipo do próprio resultado do sizeof:
#include <stdio.h>
int main(void) {
printf("char: %zu\n", sizeof(char));
printf("short: %zu\n", sizeof(short));
printf("int: %zu\n", sizeof(int));
printf("long: %zu\n", sizeof(long));
printf("long long: %zu\n", sizeof(long long));
printf("float: %zu\n", sizeof(float));
printf("double: %zu\n", sizeof(double));
printf("long double: %zu\n", sizeof(long double));
printf("bool: %zu\n", sizeof(bool));
printf("ponteiro: %zu\n", sizeof(int *));
printf("size_t: %zu\n", sizeof(size_t));
return 0;
}
No laboratório (gcc 15, Linux de 64 bits):
$ gcc -std=c23 -Wall -Wextra tipos.c -o tipos $ ./tipos char: 1 short: 2 int: 4 long: 8 long long: 8 float: 4 double: 8 long double: 16 bool: 1 ponteiro: 8 size_t: 8
E no editor do guia (Clang compilado para WebAssembly, que é de 32 bits), o mesmo código mostra:
$ clang -std=c23 -Wall -Wextra tipos.c -o tipos $ ./tipos char: 1 short: 2 int: 4 long: 4 « 8 no gcc long long: 8 float: 4 double: 8 long double: 16 bool: 1 ponteiro: 4 « 8 no gcc size_t: 4 « 8 no gcc [código de saída: 0]
Três tipos mudaram: long, ponteiro e size_t. Rode você mesmo e confira:
Experimente: rode e compare com a saída do gcc acima. Depois acrescente uma linha para sizeof(unsigned int) e outra para sizeof(char *) (um ponteiro para texto). Algum deles muda de tamanho por não ter sinal ou por apontar para outro tipo?
#include <stdio.h>
int main(void) {
printf("int: %zu\n", sizeof(int));
printf("long: %zu\n", sizeof(long));
printf("double: %zu\n", sizeof(double));
printf("ponteiro: %zu\n", sizeof(int *));
printf("size_t: %zu\n", sizeof(size_t));
return 0;
}
Por que os tamanhos mudam?
O C nasceu em 1972 para rodar em computadores muito diferentes entre si, e por isso a linguagem não fixa o tamanho da maioria dos tipos. Ela só garante mínimos e uma ordem:
chartem 1 byte;shorteinttêm pelo menos 2;longpelo menos 4;long longpelo menos 8.sizeof(short)≤sizeof(int)≤sizeof(long)≤sizeof(long long).
Cada sistema escolhe os tamanhos dentro dessas regras. O que mais pesa é o tamanho do endereço de memória do processador:
| Sistema | int | long | ponteiro e size_t |
|---|---|---|---|
| Linux e macOS de 64 bits (o gcc do laboratório) | 4 | 8 | 8 |
| Windows de 64 bits | 4 | 4 | 8 |
| Sistemas de 32 bits e o WebAssembly do editor | 4 | 4 | 4 |
| Microcontroladores pequenos (Arduino Uno) | 2 | 4 | 2 |
Um computador "de 64 bits" usa endereços de 8 bytes (64 bits), então ponteiros e size_t (que mede tamanhos de memória) têm 8. O WebAssembly que roda no navegador usa endereços de 4 bytes (32 bits). O long acompanha o ponteiro no Linux, mas não no Windows. É por isso que o guia diz: nunca suponha o tamanho de um tipo; pergunte com sizeof.
Quando o tamanho importa (arquivos binários, comunicação com outros programas), use os tipos de <stdint.h>: int32_t tem exatamente 4 bytes e int64_t exatamente 8, em qualquer sistema. Aula Inteiros de Tamanho Fixo.
O tamanho muda também o maior valor que cabe no tipo. O <limits.h> tem esses limites (aula Inteiros):
$ cat limite.c
#include <stdio.h>
#include <limits.h>
int main(void) {
printf("INT_MAX = %d\n", INT_MAX);
printf("LONG_MAX = %ld\n", LONG_MAX);
return 0;
}
$ gcc -std=c23 -Wall -Wextra limite.c -o limite
$ ./limite
INT_MAX = 2147483647
LONG_MAX = 9223372036854775807 « no editor: 2147483647
3. O resultado: size_t e %zu
O sizeof não devolve um int. Ele devolve um size_t ("tipo de tamanho"): um inteiro sem sinal (tamanho nunca é negativo) grande o bastante para medir qualquer coisa que caiba na memória. Por isso o size_t tem o tamanho de um endereço: 8 bytes no laboratório, 4 no editor.
Você pode guardar o resultado numa variável desse tipo:
#include <stdio.h>
int main(void) {
size_t bytes = sizeof(double);
printf("Um double ocupa %zu bytes.\n", bytes);
return 0;
}
$ gcc -std=c23 -Wall -Wextra guarda.c -o guarda $ ./guarda Um double ocupa 8 bytes.
O size_t é definido em vários cabeçalhos, entre eles o <stdio.h>, <stdlib.h> e <stddef.h>; com o stdio.h incluído, ele já está disponível. Você vai encontrá-lo de novo em strlen (aula Funções de String) e em malloc (aula malloc e free).
Por que não %d?
Porque o %d é para int, e o tipo do sizeof é outro:
#include <stdio.h>
int main(void) {
int idade = 29;
printf("%d\n", sizeof idade);
printf("%lu\n", sizeof idade);
return 0;
}
$ gcc -std=c23 -Wall -Wextra formato.c -o formato
formato.c: In function ‘main’:
formato.c:5:14: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
5 | printf("%d\n", sizeof idade);
| ~^ ~~~~~~~~~~~~
| | |
| int long unsigned int
| %ld
$ ./formato
4
4
- Argument 2 has type ‘long unsigned int’: no Linux de 64 bits, o
size_té um apelido paraunsigned long. O gcc sugere%ld, mas o certo para umsize_té%zu. - O programa mostrou
4mesmo assim, por sorte: lixo que "deu certo". Não confie nisso. - O
%lunão deu aviso aqui, mas daria no Windows de 64 bits, ondesize_tnão éunsigned long. O%zué o único que está certo em todo lugar C99.
O Clang do editor avisa o mesmo, já sugerindo o formato certo: format specifies type 'int' but the argument has type '__size_t' (aka 'unsigned long') [-Wformat], com %zu embaixo.
size_t
Como o size_t não tem negativos, uma conta que "deveria" dar negativo dá a volta e vira um número enorme:
$ cat menos.c
#include <stdio.h>
int main(void) {
size_t tem = 3;
size_t precisa = 5;
printf("Faltam: %zu\n", tem - precisa);
return 0;
}
$ gcc -std=c23 -Wall -Wextra menos.c -o menos
$ ./menos
Faltam: 18446744073709551614 « no editor: 4294967294
3 − 5 virou 18 quintilhões, sem aviso nenhum. Antes de subtrair tamanhos, compare qual é o maior. A aula Conversão de Tipos mostra mais surpresas dos tipos sem sinal.
4. sizeof de uma variável ou de um valor
Além de tipos, o sizeof aceita uma variável ou qualquer expressão. Aí os parênteses são opcionais:
#include <stdio.h>
int main(void) {
double preco = 4.5;
printf("%zu\n", sizeof preco);
printf("%zu\n", sizeof(preco));
printf("%zu\n", sizeof(preco * 2));
return 0;
}
$ gcc -std=c23 -Wall -Wextra variavel.c -o variavel $ ./variavel 8 8 8
- sizeof preco
- O tamanho da variável
preco, ou seja, do tipo dela (double). Sem parênteses, porque é uma variável. - sizeof(preco)
- O mesmo, com parênteses. Os parênteses em volta de uma variável são só parênteses comuns, como em
(2 + 3). - sizeof(preco * 2)
- O tamanho do tipo do resultado da conta (um
double). A conta em si não é feita.
Perguntar pela variável (sizeof preco) em vez do tipo (sizeof(double)) tem uma vantagem: se um dia você trocar o tipo da variável, o sizeof acompanha sozinho.
O sizeof não executa a conta
O sizeof é resolvido pelo compilador, antes de o programa rodar: ele só olha o tipo. Uma surpresa disso:
$ cat naoavalia.c
#include <stdio.h>
int main(void) {
int x = 5;
size_t t = sizeof(x = 99);
printf("x = %d, t = %zu\n", x, t);
printf("%zu %zu %zu\n", sizeof 'A', sizeof 3.0f, sizeof "Ana");
return 0;
}
$ gcc -std=c23 -Wall -Wextra naoavalia.c -o naoavalia
$ ./naoavalia
x = 5, t = 4
4 4 4
- O
x = 99dentro dosizeofnunca rodou: oxcontinua 5. O Clang do editor avisa (expression with side effects has no effect in an unevaluated context, "expressão com efeito não tem efeito num contexto que não é avaliado"); o gcc, não. sizeof 'A'deu 4, e não 1: em C, uma letra entre aspas simples escrita direto no código é umint, não umchar(no C++ é diferente). Uma variávelchartem 1 byte.sizeof 3.0fé o tamanho de umfloat(4).sizeof "Ana"deu 4: as 3 letras mais o'\0'invisível que marca o fim de todo texto em C (aula Strings).
5. O tamanho de um array
Com um array (uma fileira de valores do mesmo tipo, aula Arrays), o sizeof devolve o tamanho da fileira inteira:
#include <stdio.h>
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
double precos[3] = {4.5, 2.0, 7.25};
char nome[] = "Ana";
printf("notas: %zu bytes\n", sizeof notas);
printf("precos: %zu bytes\n", sizeof precos);
printf("nome: %zu bytes\n", sizeof nome);
printf("elementos de notas: %zu\n", sizeof notas / sizeof notas[0]);
return 0;
}
$ gcc -std=c23 -Wall -Wextra array.c -o array $ ./array notas: 20 bytes precos: 24 bytes nome: 4 bytes elementos de notas: 5
- sizeof notas
- 5 inteiros × 4 bytes = 20 bytes.
- sizeof precos
- 3
double× 8 bytes = 24 bytes. - sizeof nome
- O texto
"Ana"guardado num array dechar: 3 letras + o'\0'= 4 bytes. - sizeof notas[0]
- O tamanho de um elemento (o primeiro): 4 bytes.
- sizeof notas / sizeof notas[0]
- Tamanho total ÷ tamanho de um = quantos elementos: 20 ÷ 4 = 5. Essa conta funciona para qualquer tipo, em qualquer computador.
Essa última conta é um dos usos mais comuns do sizeof: você escreve os valores do array uma vez e o programa descobre sozinho quantos são. Se acrescentar uma nota, a conta se ajusta.
Missão: a Ana anotou as temperaturas da semana. O programa diz que guardou 28, o que não faz sentido: sizeof temperaturas conta bytes, não temperaturas. Divida pelo tamanho de um elemento para mostrar Temperaturas guardadas: 7.
#include <stdio.h>
int main(void) {
int temperaturas[] = {28, 31, 30, 27, 25, 29, 32};
size_t quantas = sizeof temperaturas;
printf("Temperaturas guardadas: %zu\n", quantas);
return 0;
}
#include <stdio.h>
int main(void) {
int temperaturas[] = {28, 31, 30, 27, 25, 29, 32};
size_t quantas = sizeof temperaturas / sizeof temperaturas[0];
printf("Temperaturas guardadas: %zu\n", quantas);
return 0;
}
A pegadinha: array dentro de uma função
A conta só funciona onde o array foi criado. Quando você passa um array para uma função (aula Funções), a função recebe só o endereço do começo dele, e o sizeof mede o endereço:
#include <stdio.h>
void mostra(int v[]) {
printf("dentro da função: %zu\n", sizeof v);
}
int main(void) {
int notas[5] = {7, 8, 6, 9, 10};
printf("no main: %zu\n", sizeof notas);
mostra(notas);
return 0;
}
$ gcc -std=c23 -Wall -Wextra funcao.c -o funcao
funcao.c: In function ‘mostra’:
funcao.c:4:46: warning: ‘sizeof’ on array function parameter ‘v’ will return size of ‘int *’ [-Wsizeof-array-argument]
4 | printf("dentro da função: %zu\n", sizeof v);
| ^
funcao.c:3:17: note: declared here
3 | void mostra(int v[]) {
| ~~~~^~~
$ ./funcao
no main: 20
dentro da função: 8 « o tamanho de um ponteiro (4 no editor)
‘sizeof’ on array function parameter ‘v’ will return size of ‘int *’ = "o sizeof de um parâmetro array vai devolver o tamanho de int *" (um ponteiro). Por isso, funções que recebem arrays sempre recebem também a quantidade de elementos, num segundo parâmetro. A explicação completa está em Ponteiros e Arrays.
Formas diferentes: qual usar
| Escrita | Funciona com | Quando usar |
|---|---|---|
sizeof(int) | tipos (parênteses obrigatórios) | Para perguntar sobre um tipo em geral, como na tabela da seção 2. |
sizeof x | variáveis e expressões | Quando você tem a variável à mão. Acompanha sozinho se o tipo mudar. É o estilo de muitos programadores C experientes. |
sizeof(x) | variáveis e expressões | O mesmo que sizeof x; muita gente prefere os parênteses por costume. Os dois estão certos. |
sizeof v / sizeof v[0] | arrays, onde foram criados | Para contar os elementos de um array. Nunca dentro de uma função que recebeu o array. |
Escrever o número (4, 8) direto | - | Nunca. Funciona no seu computador e quebra no do vizinho. |
Quando dá errado
warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’
O que significa: você mostrou um sizeof (ou um size_t) com %d.
Como consertar: use %zu. No Clang, a mensagem fala em '__size_t' (aka 'unsigned long'), e a solução é a mesma.
error: expected expression before ‘int’
O que significa: você escreveu sizeof int, sem parênteses. Com tipos, eles são obrigatórios. O Clang é mais claro: expected parentheses around type name in sizeof expression ("faltam parênteses em volta do nome do tipo").
$ gcc -std=c23 -Wall -Wextra semparen.c -o semparen
semparen.c: In function ‘main’:
semparen.c:4:28: error: expected expression before ‘int’
4 | printf("%zu\n", sizeof int);
| ^~~
Como consertar: sizeof(int).
warning: ‘sizeof’ on array function parameter ‘v’ will return size of ‘int *’
O que significa: dentro de uma função, o "array" recebido é só um endereço; o sizeof devolve 8 (ou 4), não o tamanho do array.
Como consertar: calcule a quantidade de elementos onde o array foi criado e passe-a como parâmetro: mostra(notas, sizeof notas / sizeof notas[0]).
O que significa: o código supõe um tamanho, por exemplo que long tem 8 bytes ou que um ponteiro cabe num int.
Como consertar: use sizeof em vez de números fixos e, quando o tamanho exato importar, os tipos de <stdint.h> (int64_t…).
O que significa: size_t não tem negativo; 3 - 5 dá a volta para o maior valor menos 1.
Como consertar: compare antes (if (tem < precisa)) ou faça a conta num tipo com sinal, de propósito.
Referência rápida
| Escrita | Resultado (gcc, Linux 64 bits) | No editor (32 bits) |
|---|---|---|
sizeof(char) | 1 (sempre) | 1 |
sizeof(short) | 2 | 2 |
sizeof(int) | 4 | 4 |
sizeof(long) | 8 | 4 |
sizeof(long long) | 8 | 8 |
sizeof(float) / sizeof(double) | 4 / 8 | 4 / 8 |
sizeof(long double) | 16 | 16 |
sizeof(bool) | 1 | 1 |
sizeof(int *) (ponteiro) | 8 | 4 |
sizeof(size_t) | 8 | 4 |
sizeof 'A' | 4 (é um int) | 4 |
sizeof "Ana" | 4 (3 letras + '\0') | 4 |
sizeof v (array) | bytes do array inteiro | igual, se o tipo tiver o mesmo tamanho |
sizeof v / sizeof v[0] | quantidade de elementos | igual |
printf("%zu", sizeof x) | o formato certo para size_t | igual |
Mais limites e tamanhos em Tipos e Limites.
Exercícios
1. O formato
Complete com o formato certo para mostrar um sizeof:
2. Contar elementos
Complete para calcular quantos elementos tem o array idades:
3. Qual destes tamanhos é igual em todo computador?
4. double medidas[6]; Quanto vale sizeof medidas (com double de 8 bytes)?
5. O mesmo programa mostra sizeof(long) = 8 no gcc do Linux e 4 no editor do guia. Por quê?
6. Depois de int x = 1; size_t t = sizeof(x = 50);, quanto vale x?
Missão final: o estoque que se conta sozinho
Missão: a lojinha da Ana tinha 4 produtos, e o programa diz isso com um 4 escrito à mão. Chegou um produto novo, que custa 3.75. Acrescente esse preço no array e troque os números escritos à mão por sizeof: a quantidade de produtos com a conta de elementos, e os bytes com sizeof precos. A saída deve mostrar Produtos: 5 e Bytes dos preços: 40, sem avisos.
#include <stdio.h>
int main(void) {
double precos[] = {4.50, 2.00, 7.25, 12.90};
int produtos = 4;
int bytes = 32;
printf("Produtos: %d\n", produtos);
printf("Bytes dos preços: %d\n", bytes);
return 0;
}
#include <stdio.h>
int main(void) {
double precos[] = {4.50, 2.00, 7.25, 12.90, 3.75};
size_t produtos = sizeof precos / sizeof precos[0];
size_t bytes = sizeof precos;
printf("Produtos: %zu\n", produtos);
printf("Bytes dos preços: %zu\n", bytes);
return 0;
}
Repare que a solução também trocou int por size_t e %d por %zu: é o tipo e o formato certos para tamanhos. Com %d e um sizeof, o compilador avisaria.
Resumo
sizeofé um operador que diz quantos bytes ocupa um tipo ou uma variável. Para tipos, parênteses obrigatórios:sizeof(int); para variáveis, opcionais:sizeof x.- O resultado é um
size_t(inteiro sem sinal) e se mostra com%zu, nunca com%d. sizeof(char)é sempre 1; o resto depende do sistema. No gcc do Linux de 64 bits,long, ponteiro esize_ttêm 8 bytes; no editor do guia (WebAssembly de 32 bits), têm 4.- O
sizeofé resolvido na compilação e não executa a expressão.sizeof 'A'é 4 em C, esizeof "Ana"é 4 (conta o'\0'). sizeof v / sizeof v[0]conta os elementos de um array, mas só onde o array foi criado: dentro de uma função que o recebeu, osizeofmede um ponteiro.- Nunca suponha tamanhos: pergunte com
sizeof, e use<stdint.h>quando precisar de um tamanho exato.