Hoy me puse a prototipar una herramienta de línea de comandos para mapear repositorios y generar documentación con ellos —no soy fan de hacer todo web, la verdad—, y me di cuenta de que estaba usando string para casi todo: URLs para conectarme a modelos de IA, fetching de datos, mil cosas más. Y ahí me llegó el momento de iluminación: ¿cómo funciona una cadena de texto si el computador no entiende lenguaje humano?
En tiempos de IA volver a los fundamentos se está poniendo medio obsesivo, lo sé. Pero ¿fundamentos de qué? De lo que realmente está pasando debajo de nuestro trabajo. Lenguajes que usamos todos los días —Python, NodeJS y derivados— nos hacen dar por sentadas un montón de cosas: cadenas, arreglos, los sistemas numéricos. Las usamos sin preguntarnos cómo las interpreta el intérprete o el compilador por debajo. Y resulta que cadenas, arreglos y números son mucho más parecidos de lo que uno cree.
Partamos por lo obvio: declarar un string es trivial en cualquier lenguaje.
palabra = "Hola mundo"
print(palabra)
# Resultado: Hola mundoEn JavaScript, NodeJS o TypeScript es básicamente lo mismo, cambia el detalle de las palabras clave o el tipado:
const palabra = "Hola mundo";
console.log(palabra);
// Resultado: Hola mundoconst palabra: string = "Hola mundo";
console.log(palabra);
// Resultado: Hola mundoDistintas formas de declarar, misma pregunta: ¿qué hace que esto funcione, si el computador trabaja con números y no con lenguaje humano?
La respuesta corta: tu computador nunca vio "Hola mundo". Vio una fila de números.
Abajo del todo, una máquina solo entiende electricidad: encendido o apagado, 1 o 0. Para representar texto necesitamos un acuerdo que diga "este número significa esta letra". Eso es una codificación.
El acuerdo clásico es ASCII: a cada carácter le asigna un número entre 0 y 127. La H es 72, la o es 111, la l es 108, la a es 97, el espacio es 32. Así que "Hola mundo", para tu computador, es esto:
72 111 108 97 32 109 117 110 100 111Diez números, diez bytes, uno detrás del otro en memoria. Eso es un string: una secuencia de números guardados en posiciones contiguas. Si pudiéramos hackear tu memoria RAM y mirar físicamente qué hay dentro de esas casillas contiguas mientras corre tu programa, la realidad se vería exactamente así:
| Dirección en la RAM | Valor Físico (Byte) | Carácter Equivalente (ASCII) |
|---|---|---|
0x1000 | 72 | 'H' |
0x1001 | 111 | 'o' |
0x1002 | 108 | 'l' |
0x1003 | 97 | 'a' |
0x1004 | 32 | ' ' (Espacio) |
0x1005 | 109 | 'm' |
0x1006 | 117 | 'u' |
0x1007 | 110 | 'n' |
0x1008 | 100 | 'd' |
0x1009 | 111 | 'o' |
0x100A | 0 | \0 (Freno de mano) |
Pero si nos fijamos en la tabla en el último byte, tenemos un valor que no escribimos, el 0. En lenguajes de bajo nivel como C, la máquina no sabe cuánto mide el texto. Necesita un "freno de mano" automatico para saber dónde detenerse al leer. Si borramos ese cero en memoria, el computador seguirá de largo leyendo e imprimiendo la basura que encuentre en las celdas contiguas de la RAM hasta que choque con otro cero perdido o el sistema operativo mate el proceso por violación de segmento, esto nos lleva a saber que cada vez que tenemos cualquier cosa siempre tendremos un \0 a final.
#include <stdio.h>
int main() {
// Forma A: El compilador le agrega el \0 al final automáticamente
char forma_comoda[] = "Hola";
// Forma B: El directa a los fierros. Pasamos los números ASCII y el 0 de freno manual
char forma_estricta[] = {72, 111, 108, 97, 0};
printf("Forma amigable: %s\n", forma_comoda);
printf("Forma estricta (números): %s\n", forma_estricta);
return 0;
}Y acá como se unen por decir de alguna forma: cuando escribimos Python o JS, pero el Python que casi todos usamos (CPython) está escrito en C, y Node corre sobre V8, que es C++. Así que cuando haces palabra = "Hola",en las capas de más abajo hay código C reservando memoria y copiando esos bytes a casillas contiguas de la RAM, igual que recién. La única diferencia es la contabilidad: C marca el final con el \0; Python y JS prefieren anotar el largo aparte. Mismos bytes, distinta forma de saber dónde terminan. Por eso, subas a la capa que subas, siempre aterrizas en lo mismo.
El problema es que ASCII solo alcanza para inglés. ¿La ñ? ¿La á? ¿Un 😀? No caben en 128 números. Para eso llegó Unicode, una tabla gigante que le asigna un número único a cada carácter que existe, y UTF-8, que toma esos números y los guarda usando de 1 a 4 bytes según haga falta (y lo bonito: el rango de ASCII sigue ocupando 1 byte, así que es retrocompatible).
Y acá conectamos con lo de recién: un string es, en el fondo, un arreglo de números. Por eso puedes indexarlo (palabra[0]), recorrerlo con un for y pedirle su largo —son las mismas operaciones que harías sobre un array, porque debajo es lo mismo. La única diferencia entre un arreglo de números y una cadena de texto es el acuerdo que usamos para leerlos.