2
votes

Comment vérifier «\ n» à l'aide de scanf

Toutes mes excuses C noob ici,

J'essaye de valider l'entrée utilisateur de stdin mais je ne trouve pas un moyen de gérer une seule entrée \ n . Pour simplifier complètement le problème, j'ai écrit ci-dessous. Lorsque \ n est entré, scanf se termine, s'il s'agit du premier caractère entré, par exemple, un utilisateur frappe immédiatement entrer, 6 sera généré par le Instruction printf .

int main(){
    char x[6];
    scanf("%[^\n]s", x);
    printf("%i\n", strlen(x));
    return 0;
}

Mon problème est que je ne trouve pas de moyen de savoir si un utilisateur a entré un \ n ou 6 personnages légitimes. Je voudrais également définir une vérification d'entrée MAX et je ne suis pas sûr si scanf est la fonction d'écriture avec laquelle prendre les entrées de l'utilisateur dans ce cas ou fgets peut être mieux adapté.

c

9 commentaires

Utilisez fgets


Notez que "% [^ \ n] s" n'est pas un spécificateur de format valide. Vous lisez une chaîne avec "% [^ \ n]" ou avec "% s" mais la vôtre est un hybride.


@WeatherVane C'est valide; c'est le spécificateur % [ suivi de la correspondance d'un s


@MM le s ne fait pas partie du spécificateur de format % [] et je suis convaincu que l'intention n'est pas de correspondre à un 's' . C'est une erreur fréquente.


Ne le fais pas. En tant que "C noob" autoproclamé, vous devriez sérieusement envisager de ne pas utiliser du tout scanf. Déjà. Il est difficile à comprendre pour les débutants et à bien utiliser. Vous passerez plus de temps à apprendre les faiblesses du langage scanf qu'à apprendre C. Vous en apprendrez beaucoup plus si vous utilisez simplement fread ou fgets et analysez les données vous-même.


scanf a peut-être servi un objectif il y a 30 ans, mais l'OMI n'a plus de raison d'exister. Si vous avez une application où scanf pourrait être utilisé, vous ne devriez probablement pas l'écrire du tout en C.


concernant: scanf ("% [^ \ n] s", x); 1) toujours vérifier la valeur retournée (pas les valeurs de paramètre) pour déterminer si l'opération a réussi. La famille de fonctions scanf () renvoie le nombre d'opérations réussies de 'spécificateur de format d'entrée' 2) lors de l'utilisation de '% s' et / ou '% [...]' incluent toujours un modificateur MAX CHARACTERS soit 1 de moins que la longueur du tampon d'entrée. En effet, ces spécificateurs de format ajoutent toujours un octet NUL à l'entrée. Cela évite également toute possibilité de débordement de tampon et le comportement indéfini résultant


Remarque: «x» ne comporte que 6 caractères, le nombre maximum de caractères que l'utilisateur peut saisir (sans dépassement de mémoire tampon) est donc de 5 caractères. L'instruction scanf () doit être: if (scanf ("% 5 [^ \ n]", x)! = 1) {handle error} Une erreur possible est le premier caractère de l'utilisateur est un '\ n'


concernant: printf ("% i \ n", strlen (x)); si l'utilisateur a seulement entré un '\ n' Alors cette instruction affichera 0 . Bien sûr, le code aurait dû vérifier la valeur renvoyée par scanf () et ne jamais se soucier d'appeler cette instruction


4 Réponses :


0
votes

Lorsque \ n est entré scanf se termine, s'il s'agit du premier caractère entré, par exemple Un utilisateur frappe immédiatement enter, 6 sera affiché par l'instruction printf.

Comportement non défini. Rien n'a été lu dans x [] non initialisé, donc strlen (x) est UB.


scanf ("% [^ \ n] s ", x); n'a pas de protection de largeur. Il n'a aucun contrôle de la valeur de retour. Détecter uniquement un '\ n' n'est pas anodin.

Utilisez fgets().

int main(){
  // char x[6];
  char x[7];  // add at least 1 (useful for the \n code may read)
  // scanf("%[^\n]s", x);
  if (fgets(x, sizeof x, stdin)) {
    x[strcspn(x, "\n")] = '\0'; // lop off potential trailing \n
    // printf("%i\n", strlen(x));
    printf("%zu\n", strlen(x));  // use matching specifier
    return 0;
  }
}

0 commentaires

0
votes
char *removetrailinglineends(char *str)
{
    size_t len = strlen(str);
    char *end = str + !len ? str : str + len - 1;

    while(end > str)
    {
        if(*end == '\n' || *end == '\r') *end = 0;
        end--;
    }
}

0 commentaires

1
votes

Vous pouvez utiliser getchar () pour vérifier chaque caractère d'entrée individuel et arrêter de prendre des entrées lorsque la taille d'entrée est de 6 ou que l'utilisateur saisit \ n .

Modifier: j'ai mis à jour mon code conformément à la suggestion de @ chux. Vous devez augmenter la taille du tableau à 7 afin que le dernier caractère puisse être le terminateur nul lorsque l'entrée dépasse 6 caractères. J'ai également utilisé une variable int c pour stocker les valeurs de retour de getchar au lieu de les stocker directement dans x [i] afin qu'il puisse identifier EOF .

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

int main(){
    char x[7];
    int c;
    size_t i = 0;
    while (i < 6 && (c=getchar()) != EOF) {
       x[i] = c;
       if (x[i] == '\n') {
           break;
       }
       ++i; 
    }
    x[i] = '\0'; // null-terminate string
    printf("%s\n", x);
    printf("%lu\n", strlen(x));
    return 0;
}


2 commentaires

J'aime getchar () pour cela, cela vous donne un contrôle total.


@chux Merci pour votre suggestion. J'ai mis à jour ma réponse pour éviter les problèmes que vous avez mentionnés.



0
votes

Outre la question principale, le code présente plusieurs problèmes:

char x[6] = ""; // fill NUL chars
int n_read = scanf ("%5[^\n]", x); // ok
if (n_read < 0) error...
else if (n_read == 0) newline or EOF...
else /*1*/  x is ready...

Résoudre les problèmes un par un:

char x[6] = ""; // fill NUL chars
scanf ("%5[^\n]", x); // better

Cela se lira jusqu'à la nouvelle ligne et s'arrêtera, mais peut encore déborder de la mémoire tampon:

scanf ("%5[^\n]", x); // better

Cela évitera le débordement de tampon, en limitant la lecture à 5 caractères. Malheureusement, si l'entrée est longue, il n'y a aucune garantie qu'un terminateur nul sera écrit à la fin. De plus, si scanf échoue (comme dans votre cas), alors x ne sera pas initialisé:

scanf("%[^\n]", x); // still not good

Cela fonctionnera mal si après la lecture de 3 caractères, par exemple, il y aura un IO problème. Pour lutter contre ce genre de problèmes, vérifiez le code de retour:

int main(){
    char x[6];
    // 1. an attack vector: lines with more than 6 characters will exceed buffer size
    // 2. The s will never be matched, 
    //      since %[^\n] consumes everything until EOF or \n
    // the following char can't be 's'      
    scanf("%[^\n]s", x);
    // if scanf fails, then x can be uninitialized:
    printf("%i\n", strlen(x));
    return 0;
}

Notez qu'au lieu d'initialiser x = "" , il est probablement préférable de set x [5] = '\ 0' après scanf. J'ai choisi l'option d'initialisation à la place, car il était plus facile de présenter la réponse progressivement de cette façon.


4 commentaires

concernant: // si scanf échoue, alors x peut être non initialisé: Ce n'est pas vrai, le premier caractère du tampon 'x []' sera '\ 0'.


@ user3629249 vous vous trompez. Regardez wandbox.org/permlink/E9hs0afnLJJ9rRgW


C'est pourquoi j'ai déclaré, dans mes commentaires, que la valeur renvoyée par scanf () doit être vérifiée. L'exemple référencé ne vérifie pas correctement la valeur renvoyée. Par conséquent, l'exemple référencé est «un très mauvais exemple»


@ user3629249 et je l'ai abordé dans ma réponse