Un nombre de usuario de ocho letras
Una cadena de texto en C es una fila de bytes que termina en un byte con valor cero. Si olvidas ese cero, o escribes más bytes de los que caben, el programa lee o rompe memoria que no es suya.
Una web de matrícula guarda el nombre de usuario en un hueco de 8 bytes. Alguien escribe un nombre de 20 letras, y los 12 bytes sobrantes caen encima de otros datos.
Al final de esta lección
Nota
Puedo explicar un array como memoria y una cadena como bytes que acaban en cero.
Palabras clave
| Palabra | Qué significa | Ejemplo |
|---|---|---|
| Array | elementos del mismo tipo guardados uno tras otro | int v[5] |
| Cadena | bytes de texto que terminan en un byte con valor 0 | |
| Terminador | el byte 0 que marca el final de una cadena | |
| strlen | la función que cuenta los bytes hasta el terminador | |
| Búfer | un hueco de memoria de tamaño fijo para unos datos | |
| Desbordamiento | escribir más allá del final de un búfer |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Un array son elementos seguidos en la memoria
Un array guarda varios elementos del mismo tipo, uno tras otro, sin huecos. Un int v[5] ocupa 5 por 4, o sea 20 bytes.
El elemento número i está i elementos después del primero. Por eso contar empieza en 0: el primer elemento está a distancia cero. C no comprueba si i está dentro del array: eso lo tienes que vigilar tú.
Una cadena son bytes que terminan en cero
C no tiene un tipo texto. Una cadena es un array de char cuyo último byte vale 0, el terminador.
La palabra "hola" son cuatro letras y un cero: cinco bytes. La función strlen cuenta hasta el terminador, sin contarlo, y da 4. Pero sizeof da 5, porque cuenta todo el array.
Ejemplo resuelto: la cadena "hola"
| Posición | Letra | Código guardado |
|---|---|---|
| 0 | h | 104 |
| 1 | o | 111 |
| 2 | l | 108 |
| 3 | a | 97 |
| 4 | el terminador | 0 |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Entra en la 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 cad.c: "hola" byte a byte
printf '#include <stdio.h>\n#include <string.h>\nint main(void) {\n char s[] = "hola";\n printf("%%zu %%zu\\n", sizeof(s), strlen(s));\n for (int i = 0; i < 5; i++) printf("%%d ", s[i]);\n printf("\\n");\n return 0;\n}\n' > cad.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Compila cad.c con avisos
gcc -Wall -o cad cad.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta cad
./cadSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Cuenta una cadena como lo hace strlen
# Una cadena de C: bytes seguidos que terminan en 0.
memory = [104, 111, 108, 97, 0, 55, 55, 0]
length = 0
while memory[length] != 0:
length = length + 1
print("longitud:", length)
print("bytes que ocupa:", length + 1)
# Cambia el 0 de la posición 4 por 33 y vuelve a ejecutar: ahora cuenta hasta el 0 que sigue.
Escribe cero.c: un cero dentro del texto
printf '#include <stdio.h>\n#include <string.h>\nint main(void) {\n char s[] = "ab\\0cd";\n printf("%%zu %%zu\\n", sizeof(s), strlen(s));\n return 0;\n}\n' > cero.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Compila cero.c
gcc -Wall -o cero cero.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta cero: 6 y 2
./ceroSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Un texto largo en un búfer corto desborda
Un búfer tiene un tamaño fijo. Copiar una cadena más larga que el búfer escribe más allá de su final: un desbordamiento.
Esos bytes caen sobre otras variables, o sobre datos que el programa necesita para volver de una función. El resultado no está definido: puede parecer que funciona, o romperse. No lo ejecutamos; la solución es comprobar el tamaño antes de copiar.
Ejemplo: 16 bytes en un búfer de 5
| Dato | Bytes | Dónde cae |
|---|---|---|
| Búfer de destino | 5 | posiciones 0 a 4 |
| Texto "mesa de estudio" | 15 letras y el cero: 16 | posiciones 0 a 15 |
| Bytes que sobran | 11 | posiciones 5 a 15, fuera del búfer |
| Con strncpy y un máximo de 4 | 4 letras y un cero a mano | posiciones 0 a 4, dentro |
Las celdas que rellenan los estudiantes quedan en blanco en esta vista previa.
Escribe corta.c, que copia con límite
printf '#include <stdio.h>\n#include <string.h>\nint main(void) {\n char destino[5];\n strncpy(destino, "mesa de estudio", 4);\n destino[4] = 0;\n printf("%%s %%zu\\n", destino, strlen(destino));\n return 0;\n}\n' > corta.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Compila corta.c
gcc -Wall -o corta corta.cSe muestra aquí y no se ejecuta. Crea una cuenta gratuita para pulsar Ejecutar y escribirlo en el terminal de tu propio laboratorio.
Ejecuta corta: mesa 4
./cortaSe 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 sizeof y strlen dan lo mismo para una cadena. sizeof da el tamaño del array, con el terminador. strlen cuenta hasta el terminador, sin él. Para "hola" son 5 y 4. Y cuando copies, reserva siempre un byte más para el cero.