¿Cómo deben usarse las matrices de caracteres como cadenas?

10

Entiendo que las cadenas en C son solo matrices de caracteres. Así que probé el siguiente código, pero da resultados extraños, como la salida de basura o los bloqueos del programa:

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

¿Por qué esto no funciona?

Se compila limpiamente con gcc -std=c17 -pedantic-errors -Wall -Wextra.


Nota: Esta publicación está destinada a ser utilizada como una pregunta frecuente canónica para problemas derivados de una falla en la asignación de espacio para un terminador NUL al declarar una cadena.

Lundin
fuente

Respuestas:

12

La cadena de CA es una matriz de caracteres que termina con un terminador nulo .

Todos los caracteres tienen un valor de tabla de símbolos. El terminador nulo es el valor del símbolo 0(cero). Se usa para marcar el final de una cadena. Esto es necesario ya que el tamaño de la cadena no se almacena en ningún lado.

Por lo tanto, cada vez que asigne espacio para una cadena, debe incluir suficiente espacio para el carácter de terminación nulo. Su ejemplo no hace esto, solo asigna espacio para los 5 caracteres de "hello". El código correcto debe ser:

char str[6] = "hello";

O, de manera equivalente, puede escribir código autodocumentado para 5 caracteres más 1 terminador nulo:

char str[5+1] = "hello";

Al asignar memoria para una cadena dinámicamente en tiempo de ejecución, también debe asignar espacio para el terminador nulo:

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

Si no agrega un terminador nulo al final de una cadena, las funciones de la biblioteca esperan que una cadena no funcione correctamente y obtendrá errores de "comportamiento indefinido", como salida de basura o bloqueos del programa.

La forma más común de escribir un carácter nulo terminador en C es mediante el uso de una denominada "secuencia de escape octal", con este aspecto: '\0'. Esto es 100% equivalente a la escritura 0, pero \sirve como código autodocumentado para indicar que el cero está explícitamente destinado a ser un terminador nulo. Código como if(str[i] == '\0')comprobará si el carácter específico es el terminador nulo.

¡Tenga en cuenta que el término terminador nulo no tiene nada que ver con punteros nulos o la NULLmacro! Esto puede ser confuso: nombres muy similares pero significados muy diferentes. Esta es la razón por la cual el terminador nulo a veces se denomina NULcon una L, que no debe confundirse con NULLpunteros nulos. Consulte las respuestas a esta pregunta SO para obtener más detalles.

El "hello"en su código se llama un literal de cadena . Esto debe considerarse como una cadena de solo lectura. La ""sintaxis significa que el compilador agregará un terminador nulo al final del literal de cadena automáticamente. Entonces, si imprime sizeof("hello"), obtendrá 6, no 5, porque obtiene el tamaño de la matriz, incluido un terminador nulo.


Se compila limpiamente con gcc

De hecho, ni siquiera una advertencia. Esto se debe a un sutil detalle / falla en el lenguaje C que permite que las matrices de caracteres se inicialicen con un literal de cadena que contiene exactamente tantos caracteres como hay espacio en la matriz y luego descarta silenciosamente el terminador nulo (C17 6.7.9 / 15). El lenguaje se comporta así intencionalmente por razones históricas, consulte Diagnóstico inconsistente de gcc para la inicialización de cadenas para obtener más detalles. También tenga en cuenta que C ++ es diferente aquí y no permite que se use este truco / falla.

Lundin
fuente
1
Deberías mencionar el char str[] = "hello";caso.
Jabberwocky
@Jabberwocky Esta es una wiki comunitaria, no dudes en editar y contribuir.
Lundin el
1
... y quizás también el char *str = "hello";... str[0] = foo;problema.
Jabberwocky
Quizás extienda la implicación de usar sizeofa su uso en un parámetro de función, especialmente cuando se define como una matriz.
Veleta
@WeatherVane Debería estar cubierto por otra pregunta frecuente aquí: stackoverflow.com/questions/492384/…
Lundin
4

Del Estándar C (7.1.1 Definiciones de términos)

1 Una cadena es una secuencia contigua de caracteres terminados por e incluyendo el primer carácter nulo. El término cadena multibyte a veces se usa para enfatizar el procesamiento especial dado a los caracteres multibyte contenidos en la cadena o para evitar la confusión con una cadena ancha. Un puntero a una cadena es un puntero a su carácter inicial (con la dirección más baja). La longitud de una cadena es el número de bytes que preceden al carácter nulo y el valor de una cadena es la secuencia de los valores de los caracteres contenidos, en orden.

En esta declaración

char str [5] = "hello";

el literal de cadena "hello"tiene la representación interna como

{ 'h', 'e', 'l', 'l', 'o', '\0' }

entonces tiene 6 caracteres incluyendo el cero final. Sus elementos se utilizan para inicializar la matriz de caracteres strque reservan espacio solo para 5 caracteres.

El estándar C (opuesto al estándar C ++) permite tal inicialización de una matriz de caracteres cuando el cero final de un literal de cadena no se usa como inicializador.

Sin embargo, como resultado, la matriz de caracteres strno contiene una cadena.

Si desea que la matriz contenga una cadena, puede escribir

char str [6] = "hello";

o solo

char str [] = "hello";

En el último caso, el tamaño de la matriz de caracteres se determina a partir del número de inicializadores del literal de cadena que es igual a 6.

Vlad de Moscú
fuente
0

¿Se pueden considerar todas las cadenas como una matriz de caracteres ( ), se pueden considerar todas las matrices de caracteres como cadenas ( No ).

¿Por qué no? y ¿por qué importa?

Además de las otras respuestas que explican que la longitud de una cadena no se almacena en ninguna parte como parte de la cadena y las referencias al estándar donde se define una cadena, la otra cara es "¿Cómo manejan las cadenas las funciones de la biblioteca de C?"

Si bien una matriz de caracteres puede contener los mismos caracteres, es simplemente una matriz de caracteres a menos que el último carácter sea seguido por el carácter de terminación nula . Ese carácter de terminación nula es lo que permite que la matriz de caracteres se considere (se maneje como) una cadena.

Todas las funciones en C que esperan una cadena como argumento esperan que la secuencia de caracteres sea terminada en nulo . ¿Por qué?

Tiene que ver con la forma en que funcionan todas las funciones de cadena. Como la longitud no se incluye como parte de una matriz, las funciones de cadena, escanee hacia adelante en la matriz hasta que se encuentre el carácter nulo (por ejemplo '\0', equivalente a decimal 0). Ver tabla y descripción ASCII . Independientemente de si está utilizando strcpy, strchr, strcspn, etc .. Todas las funciones de cadena se basan en el nulo de terminación de carácter estar presente para definir dónde el final de esa cadena es.

Una comparación de dos funciones similares de string.henfatizará la importancia del carácter de terminación nula . Toma por ejemplo:

    char *strcpy(char *dest, const char *src);

La strcpyfunción simplemente copia bytes desde srchasta desthasta que se encuentra el carácter de terminación nula que indica strcpydónde detener la copia de caracteres. Ahora tome la función similar memcpy:

    void *memcpy(void *dest, const void *src, size_t n);

La función realiza una operación similar, pero no considera ni requiere que el srcparámetro sea una cadena. Como memcpyno puede simplemente avanzar en la srccopia de bytes desthasta que se alcanza un carácter de terminación nula , se requiere un número explícito de bytes para copiar como tercer parámetro. Este tercer parámetro proporciona memcpyla misma información de tamaño que strcpypuede derivarse simplemente escaneando hacia adelante hasta que se encuentre un carácter de terminación nula .

(que también enfatiza lo que sale mal strcpy(o cualquier función que espera una cadena) si no proporciona la función con una cadena terminada en nulo; no tiene idea de dónde detenerse y felizmente correrá por el resto de su segmento de memoria la invocación de un comportamiento indefinido hasta que un nul caracteres sólo pasa a ser encontrado en algún lugar de la memoria - o se produce un fallo de segmentación)

Es por eso que las funciones que esperan una cadena terminada en nulo deben pasar una cadena terminada en nulo y por qué es importante .

David C. Rankin
fuente
0

Intuitivamente ...

Piense en una matriz como una variable (contiene cosas) y una cadena como un valor (se puede colocar en una variable).

Ciertamente no son lo mismo. En su caso, la variable es demasiado pequeña para contener la cadena, por lo que la cadena se corta. (Las "cadenas entre comillas" en C tienen un carácter nulo implícito al final).

Sin embargo, es posible almacenar una cadena en una matriz que es mucho más grande que la cadena.

Tenga en cuenta que los operadores habituales de asignación y comparación ( = == <etc.) no funcionan como cabría esperar. Pero la strxyzfamilia de funciones se acerca bastante, una vez que sabes lo que estás haciendo. Consulte las preguntas frecuentes de C sobre cadenas y matrices .

Artelius
fuente