En C, una “cadena de texto” no existe como un tipo de dato primitivo; lo que manejamos son arrays de caracteres que terminan con un valor especial llamado carácter nulo ('\0'). Las funciones de <string.h> son herramientas diseñadas para buscar, copiar, comparar y manipular estos arrays.
Para entender cómo funcionan, debes comprender que la mayoría de estas funciones no saben cuánto mide un array; simplemente recorren la memoria byte a byte hasta que encuentran el '\0'. Por ello, si olvidas ese carácter o intentas escribir más allá del espacio reservado, el programa seguirá leyendo o escribiendo en memoria que no le pertenece, provocando errores catastróficos.
Si necesitas manipular texto, usarás funciones como strlen para medir o strcmp para comparar. Si vas a mover bloques de datos, usarás memcpy o memmove. El peligro surge cuando no controlas el tamaño del destino o cuando intentas copiar una cadena en un espacio insuficiente: el resultado es un desbordamiento de búfer (buffer overflow), un error que compromete la seguridad y la estabilidad del sistema.
#define _POSIX_C_SOURCE 200809L // Para strtok_r [POSIX]
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
// Un log de ejemplo: [INFO] id:1024:mensaje
char log_entrada[] = "[INFO] id:1024:mensaje";
char buffer_destino[32];
char *token;
char context[10]; // Contexto para strtok_r [POSIX]
// 1. Medición y comparación
size_t longitud = strlen(log_entrada);
printf("Log original: %s (Longitud: %zu)\n", log_entrada, longitud);
if (strncmp(log_entrada, "[INFO]", 6) == 0) {
printf("El log es de tipo INFO.\n");
}
// 2. Búsqueda de caracteres y substrings
char *colon_ptr = strchr(log_entrada, ':'); // Busca la primera ':'
if (colon_ptr != NULL) {
printf("Primer ':' encontrado en la posición: %ld\n", colon_ptr - log_entrada);
}
char *sub_string = strstr(log_entrada, "1024"); // Busca el substring
if (sub_string != NULL) {
printf("ID encontrado: %s\n", sub_string);
}
// 3. Tokenización (dividir la cadena)
// Usamos strtok_r por ser reentrante [POSIX]
printf("Tokens extraídos:\n");
token = strtok_r(log_entrada, ":", context);
while (token != NULL) {
printf(" Token: %s\n", token);
token = strtok_r(NULL, ":", context);
}
// 4. Copia segura y manipulación de memoria
// Usamos memset para limpiar el buffer con ceros
memset(buffer_destino, 0, sizeof(buffer_destino));
// strncpy copia hasta n, pero no garantiza el '\0' si el origen es largo
// Por eso, siempre es buena práctica asegurar el terminador manualmente.
strncpy(buffer_destino, "Texto corto", sizeof(buffer_destino) - 1);
buffer_destino[sizeof(buffer_destino) - 1] = '\0';
printf("Buffer con strncpy: '%s'\n", buffer_destino);
// 5. Memoria con regiones solapadas: memcpy vs memmove
char arreglo[] = "ABCDE";
// Queremos mover "BCD" una posición a la derecha para obtener "ABBCDE"
// Usamos memmove porque las regiones [0-3] y [1-4] se solapan
memmove(arreglo + 1, arreglo, 3);
printf("Arreglo tras memmove: %s\n", arreglo);
// 6. Comparación de memoria cruda
char datos1[] = {0x01, 0x02, 0x03};
char datos2[] = {0x01, 0x02, 0x03};
if (memcmp(datos1, datos2, 3) == 0) {
printf("Los bloques de memoria son idénticos.\n");
}
return 0;
}
Análisis del funcionamiento
En el código anterior, hemos visto cómo interactúa la memoria con las funciones. Al usar strlen(log_entrada), el procesador recorre log_entrada hasta detectar el valor 0 que marca el fin. Si log_entrada no tuviera ese \0, strlen seguiría leyendo hasta encontrar un cero accidental en la memoria, devolviendo un valor incorrecto o provocando un error de segmentación.
Al utilizar strtok_r, estamos realizando una tokenización destructiva. Esto significa que la función inserta caracteres \0 directamente en el string original para separar las partes. Por eso, después de usar strtok_r, el array log_entrada ha sido modificado; ya no es el mismo string original. La versión strtok_r [POSIX] requiere un puntero context para guardar el estado de la iteración, lo que permite usarla de forma segura en programas con múltiples hilos (thread-safe), a diferencia de la versión clásica strtok.
Un punto crítico es la diferencia entre memcpy y memmove. En el ejemplo, al desplazar caracteres en el arreglo, las áreas de origen y destino se solapan. memcpy no está diseñado para esto y el resultado es un comportamiento indefinido porque el compilador/CPU podría intentar copiar datos que aún no han sido actualizados por la propia copia. memmove, en cambio, garantiza la integridad al tratar la copia como si se moviera a un búfer temporal, permitiendo solapamientos sin errores.
El error frecuente
Un error clásico al usar strncpy es asumir que siempre añade un terminador nulo al final.
char destino[5];
strncpy(destino, "HOLA_MUNDO", 5);
// ERROR: 'destino' ahora contiene {'H', 'O', 'L', 'A', '_'}
// ¡NO TIENE '\0'!
printf("%s", destino); // Imprimirá "HOLA_" seguido de basura de la memoria.
Si el tamaño n de strncpy es igual o menor que la longitud de la cadena origen, no se escribirá el carácter nulo. Esto hará que cualquier función posterior (como printf o strlen) siga leyendo memoria descontroladamente. Para evitar esto, siempre usa strncpy limitando a sizeof(dest) - 1 y asigna manualmente dest[sizeof(dest) - 1] = '\0'. Herramientas como AddressSanitizer (-fsanitize=address) detectarán este desbordamiento de lectura de forma inmediata.
N° 57