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
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Byte | la unidad de memoria: ocho bits | |
| Dirección | el número de un byte dentro de la memoria | |
| sizeof | el operador que da el tamaño en bytes | sizeof(int) |
| Estructura | un grupo de campos guardados juntos | struct |
| Alineación | la regla de que un dato empiece en un múltiplo de su tamaño | |
| Relleno | los 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
| Tipo | Bytes | Valores distintos | Ejemplo de uso |
|---|---|---|---|
| char | 1 | 256 | una letra o un byte suelto |
| short | 2 | 65.536 | una cantidad pequeña |
| int | 4 | unos 4.300 millones | un contador, un precio en céntimos |
| long | 8 | muchísimos | una marca de tiempo |
| un puntero | 8 | una dirección | se ve en la lección 2 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Crea una carpeta de trabajo
mkdir -p ~/c && cd ~/cSe 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
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.cSe 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
gcc -o tam tam.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta tam
./tamSe 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
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.cSe 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
gcc -o bytes bytes.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta bytes
./bytesSe 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
| Campo | Tamaño | Empieza en | Por qué |
|---|---|---|---|
| a, un char | 1 | 0 | el primero |
| hueco | 3 | 1 | el int necesita un múltiplo de 4 |
| b, un int | 4 | 4 | ya está alineado |
| c, un char | 1 | 8 | cualquier posición vale |
| relleno final | 3 | 9 | el 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
# 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
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.cSe 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
gcc -o pad pad.cSe 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
./padSe 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
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.cSe 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
gcc -o off off.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta off
./offSe 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.