Comment les tableaux de caractères doivent-ils être utilisés comme chaînes?

10

Je comprends que les chaînes en C ne sont que des tableaux de caractères. J'ai donc essayé le code suivant, mais il donne des résultats étranges, tels que la sortie des ordures ou les plantages du programme:

#include <stdio.h>

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

Pourquoi ça ne marche pas?

Il compile proprement avec gcc -std=c17 -pedantic-errors -Wall -Wextra.


Remarque: Ce message est destiné à être utilisé comme une FAQ canonique pour les problèmes résultant d'un échec d'allocation de place pour un terminateur NUL lors de la déclaration d'une chaîne.

Lundin
la source

Réponses:

12

La chaîne AC est un tableau de caractères qui se termine par un terminateur nul .

Tous les caractères ont une valeur de table de symboles. Le terminateur nul est la valeur du symbole 0(zéro). Il est utilisé pour marquer la fin d'une chaîne. Cela est nécessaire car la taille de la chaîne n'est stockée nulle part.

Par conséquent, chaque fois que vous allouez de la place à une chaîne, vous devez inclure suffisamment d'espace pour le caractère de terminaison nul. Votre exemple ne fait pas cela, il alloue uniquement de la place pour les 5 caractères de "hello". Le code correct doit être:

char str[6] = "hello";

Ou de manière équivalente, vous pouvez écrire du code auto-documenté pour 5 caractères plus 1 terminateur nul:

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

Lors de l'allocation dynamique de mémoire pour une chaîne au moment de l'exécution, vous devez également allouer de l'espace pour le terminateur nul:

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

Si vous n'ajoutez pas de terminateur nul à la fin d'une chaîne, les fonctions de bibliothèque qui attendent une chaîne ne fonctionneront pas correctement et vous obtiendrez des bogues de "comportement indéfini" tels que la sortie des ordures ou les plantages du programme.

La façon la plus courante d'écrire un caractère de terminaison NULL dans C est à l'aide d' un soi-disant « séquence d'échappement octal », qui ressemble à ceci: '\0'. Cela équivaut à 100% à l'écriture 0, mais le \sert de code auto-documenté pour indiquer que le zéro est explicitement destiné à être un terminateur nul. Un code tel que if(str[i] == '\0')vérifiera si le caractère spécifique est le terminateur nul.

Veuillez noter que le terme null terminator n'a rien à voir avec les pointeurs null ou la NULLmacro! Cela peut être déroutant - des noms très similaires mais des significations très différentes. C'est pourquoi le terminateur nul est parfois appelé NULun L, à ne pas confondre avec NULLou des pointeurs nuls. Voir les réponses à cette question SO pour plus de détails.

Le "hello"dans votre code est appelé un littéral de chaîne . Cela doit être considéré comme une chaîne en lecture seule. La ""syntaxe signifie que le compilateur ajoutera automatiquement un terminateur nul à la fin du littéral de chaîne. Donc, si vous imprimez, sizeof("hello")vous obtiendrez 6, pas 5, car vous obtenez la taille du tableau, y compris un terminateur nul.


Il compile proprement avec gcc

En effet, pas même un avertissement. Cela est dû à un détail / défaut subtil dans le langage C qui permet d'initialiser les tableaux de caractères avec un littéral de chaîne qui contient exactement autant de caractères qu'il y a de place dans le tableau, puis de supprimer silencieusement le terminateur nul (C17 6.7.9 / 15). Le langage se comporte volontairement comme ceci pour des raisons historiques, voir Diagnostic gcc incohérent pour l'initialisation de la chaîne pour plus de détails. Notez également que C ++ est différent ici et ne permet pas d'utiliser cette astuce / faille.

Lundin
la source
1
Vous devez mentionner le char str[] = "hello";cas.
Jabberwocky
@Jabberwocky Ceci est un wiki communautaire, n'hésitez pas à le modifier et à y contribuer.
Lundin
1
... et peut-être aussi le char *str = "hello";... str[0] = foo;problème.
Jabberwocky
Peut-être étendre l'implication de l'utilisation sizeofà son utilisation sur un paramètre de fonction, en particulier lorsqu'il est défini comme un tableau.
Girouette
@WeatherVane Devrait être couvert par une autre FAQ ici: stackoverflow.com/questions/492384/…
Lundin
4

De la norme C (7.1.1 Définitions des termes)

1 Une chaîne est une séquence contiguë de caractères se terminant par et incluant le premier caractère nul. Le terme chaîne multi-octets est parfois utilisé à la place pour souligner le traitement spécial accordé aux caractères multi-octets contenus dans la chaîne ou pour éviter toute confusion avec une chaîne large. Un pointeur sur une chaîne est un pointeur sur son caractère initial (adressé le plus bas). La longueur d'une chaîne est le nombre d'octets précédant le caractère nul et la valeur d'une chaîne est la séquence des valeurs des caractères contenus, dans l'ordre.

Dans cette déclaration

char str [5] = "hello";

le littéral de chaîne "hello"a la représentation interne comme

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

il a donc 6 caractères, y compris le zéro de fin. Ses éléments sont utilisés pour initialiser le tableau de caractères strqui réserve de l'espace uniquement pour 5 caractères.

La norme C (opposée à la norme C ++) permet une telle initialisation d'un tableau de caractères lorsque le zéro de fin d'un littéral de chaîne n'est pas utilisé comme initialiseur.

Cependant, par conséquent, le tableau de caractères strne contient pas de chaîne.

Si vous voulez que le tableau contienne une chaîne, vous pouvez écrire

char str [6] = "hello";

ou juste

char str [] = "hello";

Dans le dernier cas, la taille du tableau de caractères est déterminée à partir du nombre d'initialiseurs du littéral de chaîne qui est égal à 6.

Vlad de Moscou
la source
0

Peut toutes les chaînes sont considérées comme un tableau de caractères ( Oui ), peuvent tous les tableaux de caractères sont considérés comme des chaînes ( No ).

Pourquoi pas? et pourquoi est-ce important?

Outre les autres réponses expliquant que la longueur d'une chaîne n'est stockée nulle part dans le cadre de la chaîne et les références à la norme où une chaîne est définie, le revers est "Comment les fonctions de la bibliothèque C gèrent-elles les chaînes?"

Bien qu'un tableau de caractères puisse contenir les mêmes caractères, il s'agit simplement d'un tableau de caractères à moins que le dernier caractère ne soit suivi du caractère de terminaison nul . Ce caractère de terminaison nul est ce qui permet au tableau de caractères d'être considéré (traité comme) comme une chaîne.

Toutes les fonctions en C qui attendent une chaîne comme argument s'attendent à ce que la séquence de caractères soit terminée par nul . Pourquoi?

Cela a à voir avec le fonctionnement de toutes les fonctions de chaîne. Étant donné que la longueur n'est pas incluse dans le cadre d'un tableau, les fonctions de chaîne parcourent le tableau vers l'avant jusqu'à ce que le caractère nul (par exemple '\0', équivalent à décimal 0) soit trouvé. Voir le tableau et la description ASCII . Peu importe si vous utilisez strcpy, strchr, strcspn, etc .. Toutes les fonctions de chaîne reposent sur la NUL de terminaison caractère étant présent pour définir où la fin de cette chaîne est.

Une comparaison de deux fonctions similaires de string.hsoulignera l'importance du caractère de terminaison nul . Prends pour exemple:

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

La strcpyfonction copie simplement les octets de srcà destjusqu'à ce que le caractère de fin nul soit trouvé indiquant strcpyoù arrêter la copie des caractères. Prenez maintenant la fonction similaire memcpy:

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

La fonction effectue une opération similaire, mais ne considère pas ou n'exige pas que le srcparamètre soit une chaîne. Puisqu'il memcpyne peut pas simplement balayer vers l'avant dans la srccopie d'octets jusqu'à destce qu'un caractère de fin nul soit atteint, il nécessite un nombre explicite d'octets pour copier comme troisième paramètre. Ce troisième paramètre fournit memcpyavec la même taille des informations qui strcpypeuvent être dérivées simplement en balayant vers l'avant jusqu'à ce qu'un caractère de terminaison nul soit trouvé.

(qui souligne également ce qui ne va pas strcpy(ou toute fonction qui attend une chaîne) si vous ne parvenez pas à fournir la fonction avec une chaîne terminée par nul - il n'a aucune idée de l'endroit où s'arrêter et se précipitera avec plaisir sur le reste de votre segment de mémoire invocation d'un comportement indéfini jusqu'à ce qu'un caractère nul se trouve juste quelque part dans la mémoire - ou qu'une erreur de segmentation se produise)

C'est pourquoi les fonctions qui attendent une chaîne terminée par nul doivent passer une chaîne terminée par nul et pourquoi c'est important .

David C. Rankin
la source
0

Intuitivement...

Considérez un tableau comme une variable (contient des éléments) et une chaîne comme une valeur (peut être placée dans une variable).

Ce n'est certainement pas la même chose. Dans votre cas, la variable est trop petite pour contenir la chaîne, donc la chaîne est coupée. (Les "chaînes entre guillemets" en C ont un caractère nul implicite à la fin.)

Cependant, il est possible de stocker une chaîne dans un tableau beaucoup plus grand que la chaîne.

Notez que les opérateurs d'affectation et de comparaison habituels ( = == <etc.) ne fonctionnent pas comme vous pouvez vous y attendre. Mais la strxyzfamille de fonctions est assez proche, une fois que vous savez ce que vous faites. Voir la FAQ C sur les chaînes et les tableaux .

Artelius
la source