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.

Você vai aprender
  • O que é um byte e por que cada tipo tem um tamanho.
  • As duas formas de usar o operador: sizeof(int) e sizeof x.
  • O tipo size_t e 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: %d no lugar de %zu, sizeof int sem parênteses e o sizeof de 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:

TipoBytes (gcc, Linux 64 bits)O que cabe
char1uma letra, ou um número de -128 a 127
int4números até cerca de 2 bilhões (para mais ou para menos)
double8nú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 printf para o resultado de sizeof. O z avisa que o valor é um size_t (seção 3) e o u diz 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:

  • char tem 1 byte; short e int têm pelo menos 2; long pelo menos 4; long long pelo 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:

Sistemaintlongponteiro e size_t
Linux e macOS de 64 bits (o gcc do laboratório)488
Windows de 64 bits448
Sistemas de 32 bits e o WebAssembly do editor444
Microcontroladores pequenos (Arduino Uno)242

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.

Precisa de um tamanho exato?

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 para unsigned long. O gcc sugere %ld, mas o certo para um size_t é %zu.
  • O programa mostrou 4 mesmo assim, por sorte: lixo que "deu certo". Não confie nisso.
  • O %lu não deu aviso aqui, mas daria no Windows de 64 bits, onde size_t nã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.

Cuidado ao subtrair 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 = 99 dentro do sizeof nunca rodou: o x continua 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 é um int, não um char (no C++ é diferente). Uma variável char tem 1 byte.
  • sizeof 3.0f é o tamanho de um float (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 de char: 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

EscritaFunciona comQuando usar
sizeof(int)tipos (parênteses obrigatórios)Para perguntar sobre um tipo em geral, como na tabela da seção 2.
sizeof xvariáveis e expressõesQuando 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õesO 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 criadosPara 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 programa dá um resultado no seu computador e outro no do colega (ou no editor)

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…).

Uma subtração de tamanhos dá um número gigante

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

EscritaResultado (gcc, Linux 64 bits)No editor (32 bits)
sizeof(char)1 (sempre)1
sizeof(short)22
sizeof(int)44
sizeof(long)84
sizeof(long long)88
sizeof(float) / sizeof(double)4 / 84 / 8
sizeof(long double)1616
sizeof(bool)11
sizeof(int *) (ponteiro)84
sizeof(size_t)84
sizeof 'A'4 (é um int)4
sizeof "Ana"4 (3 letras + '\0')4
sizeof v (array)bytes do array inteiroigual, se o tipo tiver o mesmo tamanho
sizeof v / sizeof v[0]quantidade de elementosigual
printf("%zu", sizeof x)o formato certo para size_tigual

Mais limites e tamanhos em Tipos e Limites.


Exercícios

1. O formato

Complete com o formato certo para mostrar um sizeof:

printf("\n", sizeof(double));

2. Contar elementos

Complete para calcular quantos elementos tem o array idades:

size_t n = sizeof 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 e size_t tê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, e sizeof "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, o sizeof mede um ponteiro.
  • Nunca suponha tamanhos: pergunte com sizeof, e use <stdint.h> quando precisar de um tamanho exato.