web-dev-qa-db-fra.com

Terminaison nulle du tableau de caractères

Considérez le cas suivant:

#include<stdio.h>
int main()
{
    char A[5];
    scanf("%s",A);
    printf("%s",A);
}

Ma question est de savoir si char A[5] Ne contient que deux caractères. Dites "ab", puis A[0]='a', A[1]='b' Et A[2]='\0'. Mais si l'entrée est disons, "abcde" alors où est '\0' Dans ce cas. A[5] Contiendra-t-il '\0'? Si oui, pourquoi? sizeof(A) renverra toujours 5 comme réponse. Ensuite, lorsque le tableau est plein, y a-t-il un octet supplémentaire réservé pour '\0' Qui sizeof() ne compte pas?

35
g4ur4v

Si vous tapez plus de quatre caractères, les caractères supplémentaires et le terminateur nul seront écrits en dehors de la fin du tableau, écrasant la mémoire n'appartenant pas au tableau. Il s'agit d'un débordement de tampon.

C ne vous empêche pas d'encombrer la mémoire que vous ne possédez pas. Il en résulte comportement indéfini. Votre programme pourrait faire n'importe quoi - il pourrait se bloquer, il pourrait supprimer silencieusement d'autres variables et provoquer un comportement déroutant, il pourrait être inoffensif, ou autre chose. Notez qu'il n'y a aucune garantie que votre programme fonctionnera de manière fiable ou se bloquera de manière fiable. Vous ne pouvez même pas dépendre d'un plantage immédiat.

C'est un excellent exemple de la raison pour laquelle scanf("%s") est dangereux et ne doit jamais être utilisé. Il ne connaît pas la taille de votre baie, ce qui signifie qu'il n'y a aucun moyen de l'utiliser en toute sécurité. Au lieu de cela, évitez scanf et utilisez quelque chose de plus sûr, comme fgets () :

fgets () lit au plus un caractère inférieur à la taille du flux et les stocke dans le tampon pointé par s. La lecture s'arrête après un EOF ou une nouvelle ligne. Si une nouvelle ligne est lue, elle est stockée dans le tampon. Un octet nul final ('\ 0') est stocké après le dernier caractère du tampon .

Exemple:

if (fgets(A, sizeof A, stdin) == NULL) {
    /* error reading input */
}

De manière ennuyeuse, fgets () laissera un caractère de fin de ligne ('\ n') à la fin du tableau. Vous pouvez donc également vouloir que le code le supprime.

size_t length = strlen(A);
if (A[length - 1] == '\n') {
    A[length - 1] = '\0';
}

Pouah. Une scanf("%s") simple (mais cassée) s'est transformée en une monstruosité de 7 lignes. Et c'est la deuxième leçon de la journée: C n'est pas bon pour les E/S et la gestion des chaînes. Cela peut être fait et cela peut être fait en toute sécurité, mais C donnera des coups de pied et hurlera tout le temps.

50
John Kugelman

Comme déjà souligné - vous devez définir/allouer un tableau de longueur N + 1 afin de stocker correctement N caractères. Il est possible de limiter la quantité de caractères lus par scanf. Dans votre exemple, ce serait:

scanf("%4s", A);

afin de lire max. 4 caractères de stdin.

8
harpun

Il n'y a pas de caractère réservé, vous devez donc faire attention de ne pas remplir le tableau au point où il ne peut pas se terminer par null. Les fonctions Char reposent sur le terminateur nul et vous obtiendrez des résultats désastreux si vous vous trouvez dans la situation que vous décrivez.

Une grande partie du code C que vous verrez utilisera les dérivés "n" de fonctions telles que strncpy. Depuis cette page de manuel, vous pouvez lire:

Les fonctions strcpy () et strncpy () renvoient s1. Les fonctions stpcpy () et stpncpy () renvoient un pointeur sur le caractère de fin\\ 0 de s1. Si stpncpy () ne termine pas s1 par un caractère NUL, il renvoie à la place un pointeur sur s1 [n] (qui ne fait pas nécessairement référence à un emplacement de mémoire valide.)

strlen s'appuie également sur le caractère nul pour déterminer la longueur d'un tampon de caractères. Si et quand vous manquez ce caractère, vous obtiendrez des résultats incorrects.

3
RC.

Vous vous retrouverez avec comportement indéfini.

Comme vous le dites, la taille de A sera toujours de 5, donc si vous lisez 5 ou plus chars, scanf essaiera d'écrire dans une mémoire, ce n'est pas supposé modifier.

Et non, il n'y a pas d'espace/caractère réservé pour le \0 symbole.

3
Kiril Kirov

les tableaux de caractères en c ne sont que des pointeurs vers des blocs de mémoire. Si vous dites au compilateur de réserver 5 octets pour les caractères, c'est le cas. Si vous essayez d'y mettre plus de 5 octets, cela écrasera simplement la mémoire après les 5 octets que vous avez réservés.

C'est pourquoi c peut avoir de sérieuses implémentations de sécurité. Vous devez savoir que vous n'écrivez que 4 caractères + a\0. C vous permettra d'écraser la mémoire jusqu'à ce que le programme plante.

Veuillez ne pas considérer char foo [5] comme une chaîne. Considérez-le comme un endroit pour mettre 5 octets. Vous pouvez y stocker 5 caractères sans null, mais vous devez vous rappeler que vous devez faire un memcpy (otherCharArray, foo, 5) et ne pas utiliser strcpy. Vous devez également savoir que le otherCharArray a suffisamment d'espace pour ces 5 octets.

3
Doug

Toute chaîne de plus de 4 caractères entraînera l'écriture de scanf au-delà des limites du tableau. Le comportement résultant n'est pas défini et, si vous êtes chanceux, provoquera le plantage de votre programme.

Si vous vous demandez pourquoi scanf n'arrête pas d'écrire des chaînes trop longues pour être stockées dans le tableau A, c'est parce que scanf n'a aucun moyen de savoir sizeof(A) est 5. Lorsque vous passez un tableau comme paramètre à une fonction C, le tableau se désintègre vers un pointeur pointant vers le premier élément du tableau. Il n'y a donc aucun moyen d'interroger la taille du tableau dans la fonction.

Afin de limiter le nombre de caractères lus dans le tableau, utilisez

scanf("%4s", A);
3
Praetorian