¿Te resulta más fácil leer con fondo crema, un tono de color, más contraste o un texto más grande? Elige tus colores y tu texto una sola vez, para todas las páginas.

Lección gratuita

Tamaño de variables y direcciones de memoria

Después de esta lección: Puedo decir cuántos bytes ocupa una variable y en qué lugar de la memoria está.

Lección de 55 min13 comandosLaboratorio de compiladores y arquitectura: gcc, flex, bison y RISC-V en una VM

Una ficha de 12 bytes o de 8

La memoria de un programa en C es una fila de bytes, y cada variable ocupa unos cuantos. Saber cuánto ocupa cada una explica por qué un programa gasta más memoria de la esperada.

Una tienda guarda un millón de fichas de cliente con tres campos. Con un orden cada ficha pesa 12 bytes, y con otro pesa 8. La diferencia son cuatro millones de bytes, y se arregla cambiando el orden de dos líneas.

Al final de esta lección

Nota

Puedo decir cuántos bytes ocupa una variable y en qué lugar de la memoria está.

Palabras clave

PalabraQué significaEjemplo
Bytela unidad de memoria: ocho bits
Direcciónel número de un byte dentro de la memoria
sizeofel operador que da el tamaño en bytessizeof(int)
Estructuraun grupo de campos guardados juntosstruct
Alineaciónla regla de que un dato empiece en un múltiplo de su tamaño
Rellenolos bytes vacíos que el compilador añade para alinear

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Cada tipo ocupa un número fijo de bytes

Un byte es la unidad de memoria. Cada variable en C tiene un tipo, y el tipo decide cuántos bytes ocupa.

El operador sizeof da ese tamaño en bytes, y lo calcula el compilador mientras compila. En el laboratorio, una máquina Linux de 64 bits, un char ocupa 1 byte y un int ocupa 4.

Tamaños en una máquina Linux de 64 bits

TipoBytesValores distintosEjemplo de uso
char1256una letra o un byte suelto
short265.536una cantidad pequeña
int4unos 4.300 millonesun contador, un precio en céntimos
long8muchísimosuna marca de tiempo
un puntero8una direcciónse ve en la lección 2

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Crea una carpeta de trabajo

Comando
mkdir -p ~/c && cd ~/c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribe tam.c, que imprime los tamaños

Comando
printf '#include <stdio.h>\nint main(void) {\n  printf("%%zu %%zu %%zu %%zu\\n", sizeof(char), sizeof(short), sizeof(int), sizeof(long));\n  return 0;\n}\n' > tam.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Compila tam.c

Comando
gcc -o tam tam.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Ejecuta tam

Comando
./tam

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Un int se reparte en varios bytes seguidos

Cada byte de la memoria tiene una dirección, un número que dice dónde está. Un int de 4 bytes ocupa cuatro direcciones seguidas.

Los procesadores de este laboratorio guardan primero el byte menos importante. El número 258 es 1 por 256 más 2, así que sus bytes son 2, 1, 0 y 0, en ese orden.

Escribe bytes.c: un int por dentro

Comando
printf '#include <stdio.h>\nint main(void) {\n  int n = 258;\n  unsigned char *p = (unsigned char *)&n;\n  printf("%%d %%d %%d %%d\\n", p[0], p[1], p[2], p[3]);\n  return 0;\n}\n' > bytes.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Compila bytes.c

Comando
gcc -o bytes bytes.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Ejecuta bytes

Comando
./bytes

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

El compilador añade relleno para alinear los campos

Un procesador lee más rápido un dato que empieza en un múltiplo de su tamaño. Por eso el compilador exige la alineación: un int empieza en 0, 4, 8, y así.

Si un campo no encaja, el compilador deja huecos, el relleno. Al final también rellena, hasta un múltiplo del campo mayor, para que un vector de estructuras siga alineado.

Ejemplo resuelto: char, int, char

CampoTamañoEmpieza enPor qué
a, un char10el primero
hueco31el int necesita un múltiplo de 4
b, un int44ya está alineado
c, un char18cualquier posición vale
relleno final39el total pasa de 9 a 12, múltiplo de 4

Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.

Calcula dónde empieza cada campo

Código: python
# Dónde cae cada campo de una estructura, con relleno.
SIZES = {"char": 1, "short": 2, "int": 4, "long": 8}
fields = ["char", "int", "char"]
offset = 0
for kind in fields:
    size = SIZES[kind]
    # Un campo empieza en un múltiplo de su tamaño.
    while offset % size != 0:
        offset = offset + 1
    print(kind, "empieza en", offset)
    offset = offset + size
print("sin relleno final:", offset)
# Cambia la lista a ["int", "char", "char"] y vuelve a ejecutar.

Escribe pad.c con dos estructuras

Comando
printf '#include <stdio.h>\nstruct A { char a; int b; char c; };\nstruct B { int b; char a; char c; };\nint main(void) {\n  printf("%%zu %%zu\\n", sizeof(struct A), sizeof(struct B));\n  return 0;\n}\n' > pad.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Compila pad.c

Comando
gcc -o pad pad.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Ejecuta pad: 12 y 8

Comando
./pad

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Escribe off.c: la posición de cada campo

Comando
printf '#include <stdio.h>\n#include <stddef.h>\nstruct A { char a; int b; char c; };\nint main(void) {\n  printf("%%zu %%zu %%zu\\n", offsetof(struct A, a), offsetof(struct A, b), offsetof(struct A, c));\n  return 0;\n}\n' > off.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Compila off.c

Comando
gcc -o off off.c

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Ejecuta off

Comando
./off

Se muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.

Un error frecuente

Advertencia

Mucha gente cree que el tamaño de una estructura es la suma de sus campos, así que char, int y char serían 6 bytes. El compilador alinea cada campo y rellena el final. Por eso sizeof da 12. Usa siempre sizeof, nunca la suma a mano.

Para ir más lejos

Un vector de un millón de estructuras de 12 bytes ocupa 12 millones de bytes. Reordena los campos para que ocupe 8 millones.

Prueba después con una estructura de un char, un long y otro char. Escribe tu predicción antes de compilar, y explica con una tabla dónde cae cada campo.

Sobre este laboratorio y su código: Laboratorio de compiladores y arquitectura: gcc, flex, bison y RISC-V en una VM

¿Enseñas a una clase? Asigna este módulo a tus alumnos con guía del profesor, tareas con corrección automática y un laboratorio para cada alumno.