11. Cadenas de caracteres en C

Última modificación por editor1 el 2018/09/17 10:30

Objetivos

  • Entender las propiedades de las cadenas de caracteres.
  • Comprender los métodos de tratamiento de cadenas de caracteres.
  • Conocer algunos principios básicos de la gestión segura de la memoria.

Introducción

Las cadenas de caracteres tienen un tratamiento especial en todos los lenguajes de programación. Aunque no son más que un vector de caracteres, su gran utilidad hace que en la práctica se disponga de muchos métodos destinados a tratar este tipo de datos, y en muchos lenguajes se definen como si se tratara de otro tipo de datos al nivel de los enteros o los reales. En esta guía profundizaremos más en este tipo de datos y en las diferentes opciones que nos proporciona el lenguaje C para tratarlas.

1. Cadenas de caracteres o strings

1.1. Definición

Una cadena de caracteres o string, es una secuencia de caracteres finalizada por el carácter '\0'. Esta secuencia de caracteres se almacena dentro de un vector de caracteres. Por tanto, hay que diferenciar entre el tamaño del vector y el tamaño de la cadena de caracteres. Por ejemplo, en la siguiente declaración:

char a[25]="abc";

Tenemos un vector de 25 posiciones (25 bytes). La cadena de caracteres sin embargo, solo ocupa 4 (3 + '\0') caracteres (4 bytes), y el resto no se utiliza. Por lo tanto, el tamaño del vector indica la longitud máxima de la cadena de caracteres que podemos guardar. Para saber la longitud de un string, tenemos el método strlen:

[Ejemplo 11_01]

#include <stdio.h>
#include
<string.h>

int main(int argc, char **argv) {

   char c[3]="ab";
   int len;
 
   /* Get the string length */
    len=strlen(c);

   /* Show the length */
    printf("The length is %d\n",len);
 
   return 0;
}

Tened en cuenta que para poder acceder a los métodos relacionados con el tratamiento de cadenas de caracteres, hay que añadir la librería string.h.

1.2. Asignación

Cuando trabajamos con vectores y las cadenas de caracteres no son una excepción, no podemos hacer una asignación directa. O sea, si intentamos lo siguiente:

[Ejemplo 11_02]

#include <stdio.h>

int main(int argc, char **argv) {

   char s1[3]="ab";
   char s2[3];

   /* BAD assignation */
    s2=s1;

   /* Show the values */
    printf("s1=%s, s2=%s\n", s1, s2);

   /* Change the first char in s2 */
    s2[0]='k';

   /* Show the values */
    printf("s1=%s, s2=%s\n", s1, s2);

   return 0;
}

Pueden pasar dos cosas. La primera es que el compilador detecte esta situación y nos dé un error de compilación, y la segunda es que queden asignadas a la misma zona de memoria, por lo que el segundo printf nos mostrará dos cadenas de caracteres con los valores kb, ya que al modificar s2 también habremos modificado s1. Este tema lo veremos con más profundidad cuando trabajemos con punteros. De momento, debemos ser conscientes de que no se puede hacer una asignación directa entre vectores ni cadenas de caracteres.

En el caso de las cadenas de caracteres, disponemos del método strcpy para copiar el contenido de una string a otra. La asignación correcta sería:

[Ejemplo 11_03]

#include <stdio.h>
#include
<string.h>

int main(int argc, char **argv) {

   char s1[3]="ab";
   char s2[3];

   /* Correct assignation */
    strcpy(s2,s1);
       
   /* Show the values */
    printf("s1=%s, s2=%s\n", s1, s2);

   /* Change the first char in s2 */
    s2[0]='k';

   /* Show the values */
    printf("s1=%s, s2=%s\n", s1, s2);

   return 0;
}

Hay que tener en cuenta que los vectores s1 y s2 no deben tener obligadamente la misma longitud, pero el vector s2 debe tener suficiente espacio para acomodar la cadena de caracteres que hay en s1.

1.3. Comparación

De la misma manera que en el caso de la asignación, la comparación entre dos vectores no se puede hacer con los operadores habituales (==, <, >, ...). El código siguiente nos diría que s1 y s2 no son iguales, mientras que tienen la misma cadena de caracteres:

[Ejemplo 11_04]

#include <stdio.h>

int main(int argc, char **argv) {

   char s1[3]="ab";
   char s2[3]="ab";
 
   /* Compare the two strings (ERROR)*/
   if (s1 == s2) {
        printf("EQUALS\n");
    } else {
        printf("NOT EQUALS\n");
    }

   return 0;
}

Para comparar cadenas de caracteres, tenemos el método strcmp(s1, s2). Este método recibe como parámetros las dos cadenas de caracteres s1 y s2 que queremos comparar y nos devuelve un valor entero:

  • 0 si s1 y s2 son iguales,
  • menor a 0 si s1 < s2 (en orden alfabético),
  • mayor a 0 si s1 > s2 (en orden alfabético).

Por lo tanto, el código correcto sería:

[Ejemplo 11_05]

#include <stdio.h>
#include
<string.h>

int main(int argc, char **argv) {

   char s1[3]="ab";
   char s2[3]="ab";
 
   /* Compare the two strings (ERROR)*/
   if (strcmp(s1,s2) == 0) {
        printf("EQUALS\n");
    } else {
        printf("NOT EQUALS\n");
    }

   return 0;
}

2. Programación segura

Un tema a tener en cuenta cuando trabajamos con cadenas de caracteres es no pasarnos nunca del espacio disponible en el vector. Fijaos en el siguiente programa:

[Ejemplo 11_06]

#include <stdio.h>
#include
<string.h>

int main(int argc, char **argv) {

   int a=3;
   char b='a';
   char c[3]="ab";
   int d=3;

   /* Show the initial values */
    printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d);

   /* Assign all the space. No '\0' is stored */
    strcpy(c,"abc");
    printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d);

   /* Assign more space. Memory of other variables is used */
    strcpy(c,"abcd");
    printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d);

   return 0;
}

Si ejecutáis el código veréis que el resultado es:

a=3, b=a, c=ab, d=3
a=3, b=a, c=abc, d=3
a=3, b=a, c=abcd, d=0

Fijaos en que se ha modificado el valor de la variable d. Si pensamos en la estructura de memoria de este programa, tenemos:

MemStringError.png

Cuando asignamos «abc» a la variable c, ocupamos todos los bytes que tenemos disponibles (X-6 a X-8) y, por lo tanto, no se altera el valor de ninguna otra variable. Cuando asignamos «abcd», estamos utilizando también el primer byte de la variable d (X-9), con lo que se modifica su valor.

Para evitar este tipo de situaciones, existen versiones de la mayoría de métodos que tratan con cadenas de caracteres y que permiten limitar la longitud a copiar. Estos métodos suelen añadir una «n» al nombre de la función. El siguiente código es robusto a este tipo de problemas, fijaos en que se utiliza el método strncpy y que se le indica el tamaño del vector de destino:

[Ejemplo 11_07]

#include <stdio.h>
#include
<string.h>

int main(int argc, char **argv) {

   int a=3;
   char b='a';
   char c[3]="ab";
   int d=3;

   /* Show the initial values */
    printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d);

   /* Assign all the space. No '\0' is stored */
    strncpy(c,"abc",3);
    printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d);

   /* Assign more space. Memory of other variables is NOT used */
    strncpy(c,"abcd",3);
    printf("a=%d, b=%c, c=%s, d=%d\n",a,b,c,d);

   return 0;
}

Es bueno tomar este tipo de precauciones siempre, pero en especial cuando las cadenas de caracteres se crean dentro del programa, por ejemplo concatenando valores o distintas cadenas de caracteres, o cuando las introduce el usuario.

Resumen

En esta guía hemos visto las diferentes opciones que nos proporciona el lenguaje C para tratar con cadenas de caracteres o strings.

Etiquetas:
Creado por editor1 el 2018/09/17 10:30