IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Pas d'erreur sur la fonction fgets lorsqu'on dépasse le int maxLength


Sujet :

C

  1. #1
    Membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Avril 2020
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Conseil

    Informations forums :
    Inscription : Avril 2020
    Messages : 88
    Points : 48
    Points
    48
    Par défaut Pas d'erreur sur la fonction fgets lorsqu'on dépasse le int maxLength
    Bonjour,

    Mon code source ci-dessous (extrêmement simple) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    /*
    Le But du programme ci dessous est de demandé le nom et la taille de la personne.
    On utilisera la fonction fgets() pour récupérer ses informations.
    Cette méthode est toujours à privilégier car elle est sécurisé comparé à un scanf()
    Regarder sur internet le prototype de la fonction fgets()
    Pourquoi quand on dépasse le nombre max de caractere il n'y a pas d'erreur ?
    */
     
    // BIBLIOTHEQUES :
    //===================================================
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    //===================================================
     
    // FONCTION PRINCIPALE MAIN (POINT D'ENTREE DU PROGRAMME) :
    //===================================================
    int main(void)
    {
    	// DECLARATION DES VARIABLES :
    	//===================================================
    	char prenom[10];
    	printf("Comment t'appelles tu ?\n");
     
    	// 9 caracteres et pas 10 pour reserver le caractere fin de chaine = '\0'
    	if ((fgets(prenom, 9, stdin)) != NULL)
    		printf("Tu t'appelles : %s", prenom);
     
    	else
    		printf("Erreur de saisie\n");
     
     
    	// ATTENTION fgets NE LIE QUE DES CARACTERES PAS DES NOMBRE !
    	// Ainsi le symbole 1 n'est pas vu comme le chiffre 1 mais comme le caractère 1
    	// Utiliser les fonctions de conversion string to double ou string to int ou encore string to long pour résoudre le problème
     
    	char prix[10];
    	printf("Quel est ton prix ?\n");
    	if (fgets(prix, 9, stdin) != NULL)
    		printf("Ton prix est : %g\n", strtod(prix, NULL));
     
    	else
    		printf("Erreur de saisie\n");
     
    	return 0;
    }
    //===================================================
    Lorsque je compile ce programme et que je dépasse la taille max de caractère (int maxLenght, voir le lien que je vous ai envoyé),
    il n'y a pas d'erreur qui se déclenche et mon else ne sert à rien.

    Je ne comprends pas pourquoi.
    Avez vous une explication svp ?

    Cordialement Zephyre

  2. #2
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par zephyre Voir le message
    Lorsque je compile ce programme et que je dépasse la taille max de caractère (int maxLenght, voir le lien que je vous ai envoyé),
    il n'y a pas d'erreur qui se déclenche et mon else ne sert à rien.

    Je ne comprends pas pourquoi.
    Avez vous une explication svp ?
    Ainsi, si la chaîne lue est plus longue que le buffer de réception, aucun dépassement en mémoire ne sera effectué
    Tu en demandes 9, tu en entres 150, la fonction n'en récupère quand-même que 9 (ou plutôt 8 car elle garde 1 place pour le '\0') et laisse le reste dans le buffer prêt à être récupéré au tour suivant.

    Citation Envoyé par zephyre Voir le message
    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    	char prenom[10];
    	printf("Comment t'appelles tu ?\n");
     
    	// 9 caracteres et pas 10 pour reserver le caractere fin de chaine = '\0'
    	if ((fgets(prenom, 9, stdin)) != NULL)
    Justement non, la fonction se charge elle-même de la soustraction. Car là, tu n'en récupères que 8. C'est aussi écrit dans le même lien.
    Il est à noter que le nombre de caractères pouvant être lu sera au maximum de (maxLength - 1).
    Pas la peine de nous mettre des liens que visiblement tu n'as pas lus...

    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    	char prenom[10 + 1];
    	printf("Comment t'appelles tu ?\n");
     
    	if ((fgets(prenom, 10 + 1, stdin)) != NULL)

    Citation Envoyé par zephyre Voir le message
    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    	// ATTENTION fgets NE LIE QUE DES CARACTERES PAS DES NOMBRE !
    Que signifie cette phrase ? En quoi les caractères sont-ils liés avec fgets() ???
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  3. #3
    Membre expérimenté Avatar de edgarjacobs
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Mai 2011
    Messages
    623
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 63
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Mai 2011
    Messages : 623
    Points : 1 554
    Points
    1 554
    Par défaut
    Hello,

    fgets() ne signale pas d'erreur car il n'y en a pas ! Si j'écris
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    char str[100];
    fgets(str,sizeof(str),stdin);
    fgets() lira au plus 99 caractères, et placera un '\0' en str[99]. Et ça ne génère aucune erreur si j'en entreplus de 99. Simplement, le trop plein de caractères restera dans le buffer clavier.

    Les inputs console en c ne sont pas faciles à gérer. Pour info, man fgets()
    On écrit "J'ai tort" ; "tord" est la conjugaison du verbre "tordre" à la 3ème personne de l'indicatif présent

  4. #4
    Membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Avril 2020
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Conseil

    Informations forums :
    Inscription : Avril 2020
    Messages : 88
    Points : 48
    Points
    48
    Par défaut
    Sve@r
    Pas la peine de nous mettre des liens que visiblement tu n'as pas lus...
    Je peux vous assurer que j'ai lu le lien que je vous ai envoyé.
    Cependant j'ai du mal à tout assimiler, toutes les informations.
    Veuillez m'en excuser, je n'ai pas l'habitude pourtant je sais lire ... Enfin je crois.
    Mais vous avez raison, je viens de lire plus lentement le lien que je vous ai fourni et effectivement c'est bien écrit.
    Faut vraiment que j'apprenne à lire de la doc technique sur les prototypes de fonctions.
    Et encore là j'ai de la chance car je l'ai trouvé en français.
    Merci encore pour votre précision chirurgical, c'est comme ça qu'on apprends !

    Sve@r
    Que signifie cette phrase ? En quoi les caractères sont-ils liés avec fgets() ???
    Dans le prototype de la fonction, c'est un pointeur de char.
    Ainsi tout ce qu'on récupère via une fonction fgets sera interprété comme un caractère au sens de char.
    Ça veux dire que par exemple si j'utilise la fonction fgets pour récupérer un entier par exemple 10 il faudra que je pense à le convertir avant en entier via une fonction strtoint ou un truc comme ça si je veux faire des calculs.
    C'est juste un pense bête, rien de plus.

    edgarjacobs
    Pour info, man fgets()
    Merci pour l'info y a pas mieux que le man.
    Le souci c'est que je ne suis pas trop familier pour le moment avec les termes anglais.
    Ainsi je privilégie la documentation en français.
    Je le sais pourtant car je l'ai lu mais je n'ai pas encore le bon vieux réflexe du man.
    Merci pour cette piqure de rappel !
    Et je sais également que y a pas mieux que la documentation anglaise beaucoup plus riche.

    J'ai juste une dernière question :
    Lorsque j'exécute mon programme et que je fais, alors je sais pas comment dire, vu que y a pas de dépassement mémoire autorisé par fgets(), on va dire lorsque j'entre un nombre de caractère > maxLenght -1,
    le second fgets() plante.
    Avez vous une explication ?

  5. #5
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par zephyre Voir le message
    Dans le prototype de la fonction, c'est un pointeur de char.
    Ainsi tout ce qu'on récupère via une fonction fgets sera interprété comme un caractère au sens de char.
    Ça veux dire que par exemple si j'utilise la fonction fgets pour récupérer un entier par exemple 10 il faudra que je pense à le convertir avant en entier via une fonction strtoint ou un truc comme ça si je veux faire des calculs.
    Déjà c'était de l'ironie sur ton orthographe et ta façon de conjuguer le verbe "lire" qui n'est pas un verbe du premier groupe et qui ne se conjugue donc pas "il lie". Ou alors on traduit ça par la conjugaison du verbe "lier" mais ce verbe n'a aucune signification vis à vis de fgets() et des caractères. Ca sert à ça l'orthographe, à exprimer sa pensée => "à essayer" et "a essayé" ne signifient pas du tout la même chose. Et donc ce n'est pas parce qu'on est en informatique que l'orthographe doit être négligée.
    Mais quelle différence fais-tu entre '1' (49) et 1 ??? Ou alors (dit dans l'autre sens) serais-tu capable d'écrire un clone de atoi() ???

    Citation Envoyé par zephyre Voir le message
    Lorsque j'exécute mon programme et que je fais, alors je sais pas comment dire, vu que y a pas de dépassement mémoire autorisé par fgets(), on va dire lorsque j'entre un nombre de caractère > maxLenght -1,
    le second fgets() plante.
    Avez vous une explication ?
    Il ne plante pas, il fonctionne parfaitement. Je te renvoie à mon précédent post et à cette partie de phrase "et laisse le reste dans le buffer prêt à être récupéré au tour suivant.". Donc au tour suivant (au second fgets) celui-ci récupère ce qui reste, ce qui est parfaitement normal. Et donc bien évidemment, puisqu'il reste encore des trucs à récupérer il n'attend pas ta saisie.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  6. #6
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    zephyre:

    Il y a des finesses passées sous silence dans les réponses qui t'ont été faites. Une caractéristique de fgets() est que, si fgets() a la place de le faire, il va stocker le retour à la ligne '\n' lorsque l'utilisateur presse Entrée.

    Si tu fais, comme dans ton code :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    fgets(prenom, 9, stdin);
    et que tu tapes "Albert" (6 lettres), prenom va en fait contenir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    { 'A', 'l', 'b', 'e', 'r', 't', '\n', '\0' }
    Si tu tapes "Philippe" (8 lettres), fgets termine la chaîne sans amputer les lettres composant le prénom, mais n'a pas la place de prendre le retour à la ligne (qui a aussi été tapé et que fgets() récupère s'il le peut), et prenom va alors contenir :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    { 'P', 'h', 'i', 'l', 'i', 'p', 'p', 'e', '\0' }
    et '\n' va rester dans le tampon de stdin et sera lu au prochain appel à une fonction consommant le contenu du flux stdin.

    De même, si tu tapes un mot plus grand qu'un mot de 8 lettres, '\n' ne sera pas présent dans la chaîne récupérée et sera laissé dans le tampon avec le reste des caractères non consommés.

    Du coup, si tu veux te plaindre à l'utilisateur qu'il dépasse la capacité prévue, tu peux rechercher le retour à la ligne, avec strchr() par exemple. S'il n'y est pas, c'est que la saisie a au moins laissé '\n' dans le tampon de stdin, voire plus.

    Si tu veux ignorer le reste de la saisie, tu dois purger le tampon. Une façon de faire est de faire alors ceci après fgets() :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	int c;
    	while((c = getchar()) != '\n' && c != EOF)
    		/* discard */ ;
    Tu le ne feras après fgets() que si la chaîne récupérée ne comprend pas '\n'.

    Autre chose.

    Avec fgets(), tu as donc compris que tu récupères une chaîne comme :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    { 'A', 'l', 'b', 'e', 'r', 't', '\n', '\0' }
    du coup si tu fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    printf("Prénom : [%s]\n", prenom);
    alors, tu vas afficher :

    Prénom : [Albert
    ]
    Si tu ne veux pas du retour à la ligne que fgets() intègre, il te faudra le supprimer : il y a différentes façons : https://stackoverflow.com/a/2693826/8138432

  7. #7
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par -Eks- Voir le message
    Il y a des finesses passées sous silence dans les réponses qui t'ont été faites. Une caractéristique de fgets() est que, si fgets() a la place de le faire, il va stocker le retour à la ligne '\n' lorsque l'utilisateur presse Entrée.
    Ce n'est pas une finesse ou une caractéristique particulière, cela fait partie de son comportement général. fgets() lit le buffer demandé et stocke tout ce qui s'y trouve y compris le <return> qui, quand c'est le clavier, a été tapé pour valider la saisie et quand c'est un fichier, indique la fin d'une ligne (ces deux éléments étant représentés en C par un '\n'). Il n'y avait donc pas lieu de mentionner plus particulièrement ce point par rapport aux autres.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  8. #8
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Il n'y a "pas lieu" de lui indiquer comment détecter que la saisie dépasse la capacité ?

    Il me semble pourtant que c'est ce que le PO essayait de faire depuis le début (de façon erronée en contrôlant si la fonction retourne NULL).

    Pourquoi lui indiquer comment le faire serait superflu ?

    Pour compléter ma contribution, j'ajouterai qu'utiliser la boucle de purge du tampon de stdin avec un compteur permet aussi de déterminer :

    • si elle a touné une seule fois : que ce qui a été saisi en plus est juste le '\n' et que cela n'ampute pas plus ce qui a été tapé (ex : cas de "Philippe" dans mon exemple, qui est sans conséquences)
    • si elle a tourné plus d'une fois : que d'avantage de caractères ont été tapés et que la saisie est amputée (ex : "Philippe, roi des Belges")

  9. #9
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par -Eks- Voir le message
    Il n'y a "pas lieu" de lui indiquer comment détecter que la saisie dépasse la capacité ?
    Alors déjà tu vas me faire le plaisir d'admettre que je ne t'ai pas repris sur cette partie de ton post qui concerne l'explication de ta méthode !!! Donc pour répondre à ta question, oui si tu veux, pas de souci sur ce point.

    Là où il y a souci c'est que ce n'est pas lié à une "finesse" ou une "particularité" de fgets(). C'est juste une méthode basée sur le comportement "normal" de fgets() ; méthode qui se base sur une recherche d'un caractère précis dans ce qui a été récupéré (tout récupéré absolument de la même façon, sans finesse aucune). Tu aurais dit "penses que tu valides ta saisie via <return> donc si ce <return> est présent ça veut dire que la saisie est complète et sinon non" ou une phrase équivalente je n'aurais pas réagi. C'est venir parler de "finesse" (comme si ça avait été codé exprès) alors que quiconque réfléchit un minimum comprend immédiatement qu'il n'y a aucune différence, quand on tape "Philippe<return>", à récupérer un 'P', un 'h' ou un '\n'.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  10. #10
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    Alors déjà tu vas me faire le plaisir d'admettre que je ne t'ai pas repris sur cette partie de ton post qui concerne l'explication de ta méthode !!! Donc pour répondre à ta question, oui si tu veux, pas de souci sur ce point.
    Je vais te faire plaisir : j'ai très bien compris que tu ne dis pas que le contenu de mes explications sur la façon de détecter une saisie au clavier dépassant la taille traitée par fgets() est inexact (mais je ne suis pas surhumain, s'il y a une erreur dites moi où et je rectifierai). Tu quotes la première phrase de mon post et tu affirmes "Ce n'est pas une finesse ou une caractéristique particulière" et tu conclues "Il n'y avait donc pas lieu de mentionner plus particulièrement ce point par rapport aux autres".

    Citation Envoyé par Sve@r Voir le message
    Là où il y a souci c'est que ce n'est pas lié à une "finesse" ou une "particularité" de fgets(). C'est juste une méthode basée sur le comportement "normal" de fgets() ; méthode qui se base sur une recherche d'un caractère précis dans ce qui a été récupéré (tout récupéré absolument de la même façon, sans finesse aucune). Tu aurais dit "penses que tu valides ta saisie via <return> donc si ce <return> est présent ça veut dire que la saisie est complète et sinon non" ou une phrase équivalente je n'aurais pas réagi. C'est venir parler de "finesse" (comme si ça avait été codé exprès) alors que quiconque réfléchit un minimum comprend immédiatement qu'il n'y a aucune différence, quand on tape "Philippe<return>", à récupérer un 'P', un 'h' ou un '\n'.
    Tu te bloques sur le terme "finesse". Pour toi, comme c'est "normal", fgets() n'est pas boguée et fonctionne conformément à sa doc, qui est évidente, et donc, il n'y a rien à expliquer.

    Évidemment que c'est "normal" puisque c'est ainsi que fonctionne cette fonction.

    Les "finesses" à comprendre sont contenues dans mon post, pas juste dans la phrase introductive "(...) des finesses passées sous silence dans les réponses qui t'ont été faites. Une caractéristique de fgets() est que, si fgets() a la place de le faire, il va stocker le retour à la ligne '\n' lorsque l'utilisateur presse Entrée.", qui ne fait qu'introduire le sujet en mettant déjà l'accent sur le fait que le '\n' ne figure que si fgets() a la place de le mettre, ce qui permet de comprendre qu'on doit chercher quelque chose qui n'y est pas pour répondre à sa question, ce qui est expliqué en détails dans la suite du post.

    Si le mot "finesses" te dérange, je n'ai pas d'états d'âmes d'auteur, remplace le dans ton esprit par "complexités, difficultés de compréhension ou d'utilisation" (note le pluriel dans ma phrase, que tu transformes en singulier).

    Répondre au PO en lui fournissant les moyens de détecter le dépassement ne me parait pas être optionnel, puisque c'est la raison première pour laquelle il demandait de l'aide. Je peux comprendre que ma remarque te déplaise (ou t'ai blessée, auquel cas je m'en excuse), lorsque j'ai indiqué que cela avait été passé sous silence. Je n'ai pas dit que c'était exprès pour maintenir le PO dans l'ignorance (cela peut être : par erreur, par flemme, parce qu'on pense que c'est "évident", etc.). Je ne pense pas que cela soit évident pour zephyre ou pour un débutant et suis en désaccord avec toi sur ce point.

    De plus, c'est pour lui, également, le moyen de savoir quand il doit purger le tampon de stdin pour éviter le 2ème comportement qu'il avait constaté et sur lequel il s'étonnait dans la suite du fil. Ce qui est, d'ailleurs, une réponse pratique à cette 2ème question. Contrairement à scanf() qui laisse systématiquement au moins '\n' dans stdin, la nécessité de purge n'est pas systématique, ce qui est une autre complexité de fgets() utilisée pour gérer une saisie au clavier... ou "finesse" que j'ai signalée.

  11. #11
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par -Eks- Voir le message
    Je vais te faire plaisir : j'ai très bien compris que tu ne dis pas que le contenu de mes explications sur la façon de détecter une saisie au clavier dépassant la taille traitée par fgets() est inexact (mais je ne suis pas surhumain, s'il y a une erreur dites moi où et je rectifierai). Tu quotes la première phrase de mon post et tu affirmes "Ce n'est pas une finesse ou une caractéristique particulière" et tu conclues "Il n'y avait donc pas lieu de mentionner plus particulièrement ce point par rapport aux autres".
    C'était exactement le sens de mon post. Et non, il n'y avait pas d'erreur dans tes exemples. "Philippe" + '\0' rentre dans un fgets(9) mais pas le '\n'.

    Citation Envoyé par -Eks- Voir le message
    Répondre au PO en lui fournissant les moyens de détecter le dépassement ne me parait pas être optionnel, puisque c'est la raison première pour laquelle il demandait de l'aide. Je peux comprendre que ma remarque te déplaise (ou t'ai blessée, auquel cas je m'en excuse), lorsque j'ai indiqué que cela avait été passé sous silence. Je n'ai pas dit que c'était exprès pour maintenir le PO dans l'ignorance (cela peut être : par erreur, par flemme, parce qu'on pense que c'est "évident", etc.). Je ne pense pas que cela soit évident pour zephyre ou pour un débutant et suis en désaccord avec toi sur ce point.
    Alors très exactement il demandait pourquoi fgets() ne renvoie pas NULL quand on entre plus que demandé. Sur ce point ma réponse (il ne renvoie pas NULL parce qu'il a fait son travail et laisse ce qui dépasse dans le buffer) était en adéquation complète avec sa question. Tu as tout à fait le droit de rajouter des précisions, ou d'élargir le débat sur "comment détecter une saisie incomplète" mais avec une introduction positive, style "on peut rajouter que" ou bien "il faut aussi savoir que". Effectivement sous-entendre que je lui ai caché des informations était maladroit (*). Quand je réponds à un premier post je ne dis pas tout parce que
    1. je ne pense pas à tout
    2. ça prend du temps et l'expérience m'a montré que c'était très souvent du temps perdu (après s'il montre son intérêt en revenant avec un exemple indiquant qu'il a pris en compte ma réponse et qu'il a d'autres questions ok je reste disposé à compléter)


    (*) Et le "-1" qui a suivi l'était aussi
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  12. #12
    Membre du Club
    Homme Profil pro
    Ingénieur
    Inscrit en
    Avril 2020
    Messages
    88
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Seine et Marne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur
    Secteur : Conseil

    Informations forums :
    Inscription : Avril 2020
    Messages : 88
    Points : 48
    Points
    48
    Par défaut
    Sve@r
    Déjà c'était de l'ironie sur ton orthographe et ta façon de conjuguer le verbe "lire" qui n'est pas un verbe du premier groupe et qui ne se conjugue donc pas "il lie". Ou alors on traduit ça par la conjugaison du verbe "lier" mais ce verbe n'a aucune signification vis à vis de fgets() et des caractères. Ca sert à ça l'orthographe, à exprimer sa pensée => "à essayer" et "a essayé" ne signifient pas du tout la même chose. Et donc ce n'est pas parce qu'on est en informatique que l'orthographe doit être négligée.
    Mais quelle différence fais-tu entre '1' (49) et 1 ??? Ou alors (dit dans l'autre sens) serais-tu capable d'écrire un clone de atoi() ???
    Alors PRIMO :
    Déjà je ne néglige pas l'orthographe à moins que vous considérez que une personne qui a fait une faute d'inattention sur tout un pavé néglige l'orthographe.
    Votre jugement est trop sévère et manque de justesse, mais c'est peut être pour mon bien ? Ou bien c'est du sadisme, juste pour faire du mal et rabaisser les gens. Il y a deux manières d’interpréter.
    Ce choix est à vous, je suis pas dans votre tête, la mienne me suffit amplement avec un beau bordel ...
    Et vu comme vous êtes pointilleux je n'ai pas vérifié mais je suis sûre à plus de 75% que s'il y en avait eu une autre vous vous seriez fait un plaisir de me le signaler.
    Cependant je vous remercie tout de même de me l'avoir signalé.
    J'essaierai de l'éviter à l'avenir mais je ne peux vous le garantir.
    Car moi j'ai grandi dans l'école de ce qui ne me tue pas me rends plus fort.
    J'ai toujours eu des difficultés avec l'orthographe mais ce n'est pas pour cela que je ne comprends pas la signification comme vous malgré ma faute d'orthographe et le contexte de la phrase vous avez compris le sens du mot que j'ai mal écrit.
    Dieu merci nous ne sommes pas des machines.

    DEUXIO :
    La différence que je fais entre '1' c'est que c'est le caractère 1 car généralement lorsqu'il y a un et un seul caractère celui-ci est entouré de simple guillemet.
    49 c'est un nombre il peut représenter n'importe quoi ça dépends comment justement il est interpréter.
    Si c'est en table ASCII par exemple il représente une lettre si c'est une autre table il représente peut être une autre lettre.
    1 c'est un nombre il peut représenter n'importe quoi ça dépends justement comment on l'interprète.
    Et vu qu'il n'y a pas son type devant on ne sait même pas s'il s'agit réellement du chiffre 1 mais en tout cas ça le suggère.

    TERTIO:
    serais-tu capable d'écrire un clone de atoi() ???
    Oui je l'ai déjà fait en me basant sur la table ASCII pour les entiers strictement positif en suivant les cours de Jacque Olivier Lapeyre sur YouTube.

    Sve@r
    Il ne plante pas, il fonctionne parfaitement. Je te renvoie à mon précédent post et à cette partie de phrase "et laisse le reste dans le buffer prêt à être récupéré au tour suivant.". Donc au tour suivant (au second fgets) celui-ci récupère ce qui reste, ce qui est parfaitement normal. Et donc bien évidemment, puisqu'il reste encore des trucs à récupérer il n'attend pas ta saisie.
    Ok j'ai compris bien que la notion de buffer clavier soit encore un mystère pour moi, j'ai compris le principe.
    Mais s'il reste des trucs à récupérer pourquoi il les affiches pas alors vu que je fais un printf juste derrière ?
    Je crois que -Eks- a donné la solution avec son idée de purger le tampon.
    Mais c'est encore le flou pour moi.

    -Eks-
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    int c;
    	while((c = getchar()) != '\n' && c != EOF)
    		/* discard */ ;
    Ok mais je ne sais pas ce que veux dire discard.
    D'après la traduction de word reference ça veut dire jeter ou encore se débarrasser.
    Donc en gros c'est la purge du tampon du buffer clavier.
    As tu le code complet, s'il est pas trop long ou dur ?
    Et comme ça le second fgets devrait fonctionner même si le premier "plante" ?

    -Eks-

    printf("Prénom : [%s]\n", prenom);

    alors, tu vas afficher :

    Prénom : [Albert
    ]
    Oui tu as raison, purée c'est vraiment la mouise !
    J'avais jamais pensé à ça !

    -Eks-
    Merci de m'avoir expliqué par touts ces schémas. J'ai compris vraiment beaucoup de chose pas dans le détail mais en gros.
    Pour moi tes explications n'étaient pas superflus ni artificielles mais m'ont vraiment été d'une aide précieuses.
    J'ai compris comment détecter on va dire un "dépassement" de fgets en cherchant le caractère retour à la ligne = \n.

    Après pour le reste à mon humble avis, vous vous prenez trop la tête -Eks- et Sve@r.
    En tout cas merci à vous deux pour votre précision chirurgical.

  13. #13
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Citation Envoyé par zephyre Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	int c;
    	while((c = getchar()) != '\n' && c != EOF)
    		/* discard */ ;
    Ok mais je ne sais pas ce que veux dire discard.
    D'après la traduction de word reference ça veut dire jeter ou encore se débarrasser.
    Donc en gros c'est la purge du tampon du buffer clavier.
    As tu le code complet, s'il est pas trop long ou dur ?
    Et comme ça le second fgets devrait fonctionner même si le premier "plante" ?
    (...)
    Le code est complet. Je ne l'ai pas inventé, c'est une façon standard de vider le tampon de stdin. Le commentaire "discard" est placé à cet endroit avec le point virgule en fin de ligne pour signifier que c'est exprès que l'on ne mets rien dans la boucle while. Les opérations permettant de consommer le tampon se trouvent dans le test et il n'y a rien à ajouter. On peut mettre un compteur dans cette boucle, comme expliqué dans mon 2ème post, pour savoir combien de caractères se trouvaient dans le tampon et n'ont pas été pris par fgets() faute de place. Cette information est utile dans le cas de reliquats de saisie avec fgets(), car cela permet de gérer intelligemment la situation et déterminer si le dépassement est vraiment un problème et tronque une information de valeur (autre chose qu'un '\n').

    Ce code doit être exécuté après fgets(), mais seulement dans le cas où fgets() ne récupère pas '\n' car cela signifie alors qu'il reste des choses dans le tampon, qui vont perturber la prochaine lecture du flux stdin.

    Tu utilises strchr() par exemple pour déterminer si cela est nécessaire.

    Voilà un exemple, qui montre comment articuler tout cela, et qui illustre : comment détecter si quelque chose est resté dans le tampon de stdin, si ce qui est resté tronque effectivement la saisie ou si on a juste manqué le '\n' dont on veut pas de toutes façons, quand et comment purger le tampon de stdin et quand ne pas le faire, et comment écraser le '\n' lorsqu'il y est et qu'on n'en veut pas.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #include <stdio.h>
    #include <string.h>
     
    /* version de la fonction de purge avec compteur */
    size_t clear_stdin_buffer(void) {
            int c;
            size_t count = 0;
     
            while ((c = getchar()) != '\n' && c != EOF)
                    count++;
     
            return count;
    }
     
    int main(void) {
            char prenom[9];
     
            fgets(prenom, 9, stdin);
            char * pch = strchr(prenom,'\n');
            if (!pch) {
                    if (clear_stdin_buffer() > 1) {
                            printf("Attention, votre saisie dépasse 8 caractères "
                                            "et sera tronquée\n");
                    }
            }
            else
                    *pch = '\0';
     
            printf("J'ai récupéré: [%s]\n", prenom);
     
            /* le tampon de stdin est vide à ce stade et ne viendra pas
             * perturber une autre tentative de lecture au clavier */
     
            return 0;
    }
    $ gcc -Wall -Wextra 11853755.c
    $ ./a.out
    Albert
    J'ai récupéré: [Albert]
    $ ./a.out
    Philippe
    J'ai récupéré: [Philippe]
    $ gcc -Wall -Wextra 11853755.c
    $ ./a.out
    Philippe, roi des Belges
    Attention, votre saisie dépasse 8 caractères et sera tronquée
    J'ai récupéré: [Philippe]
    Relis ce code avec la documentation de fgets() et de strchr(), ainsi que mon post initial.

    Citation Envoyé par zephyre Voir le message
    Merci de m'avoir expliquer par touts ces schémas. J'ai compris vraiment beaucoup de chose pas dans le détail mais en gros.
    Pour moi tes explications n'étaient pas superflus ni artificielles mais m'ont vraiment été précieuses.
    J'ai compris comment détecter on va dire un "dépassement" de fgets en cherchant le caractère retour à la ligne = \n.

    Après pour le reste à mon humble avis, vous vous prenez trop la tête -Eks- et Sve@r.
    En tout cas merci à vous deux pour votre précision chirurgical.
    Merci de tes remerciements :-)

    Ce qui compte pour moi c'est que tu aies appris comment détecter le cas du dépassement de la capacité d'accueil de fgets().

    Comme le dit Sve@r cela ne déclenche pas une erreur, en tout cas pas directement exprimée par la valeur de retour de fgets(), ce qui est récupéré étant simplement tronqué. La détection d'une saisie tronquée, qui a évidement un intérêt, ne se fait pas ainsi, mais en examinant ce que fgets() récupère et c'est ce que je voulais te faire comprendre pour essayer de répondre à ta préoccupation.

    Ce ne sont pas des choses triviales à comprendre quand on débute, et même quand on ne débute pas.

  14. #14
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par zephyre Voir le message
    J'ai toujours eu des difficultés avec l'orthographe mais ce n'est pas pour cela que je ne comprends pas la signification comme vous malgré ma faute d'orthographe et le contexte de la phrase vous avez compris le sens du mot que j'ai mal écrit.
    L'orthographe ne sert pas à ce que tu te comprennes mais à ce que les autres te comprennent aisément sans avoir à lagguer à revenir sans cesse au début d'une phrase parce que son sens général va à l'encontre du sens des mots qu'elle contient. Je ne dis rien pour un "il lis" au lieu de "il lit" même si là déjà j'ai du mal mais "il lie" franchement... Si tu as des difficultés avec l'orthographe c'est parce que tu ne lis pas assez de livres mais ce n'est pas aux autres de l'assumer.

    Citation Envoyé par zephyre Voir le message
    La différence que je fais entre '1' c'est que c'est le caractère 1 car généralement lorsqu'il y a un et un seul caractère celui-ci est entouré de simple guillemet.
    49 c'est un nombre il peut représenter n'importe quoi ça dépends comment justement il est interpréter.
    C'est la même chose, mais écrite de façon différente. Les deux sont du nombre. Un caractère n'est qu'un nombre codé sur 8 bits. C'est pour ça que '1' + 1 est possible. Et comme effectivement dans la table ascii ils se suivent, '1' + 1 donne '2' et '8' - '0' donne 8 et qu'on peut donc écrire une fonction comme "atoi()" qui calcule et renvoie 123 quand on lui donne "123" ({'1', '2', '3', '\0'}). On enlève '0' à chaque caractère et on multiplie par 10 pour le caractère suivant.

    Citation Envoyé par zephyre Voir le message
    Et vu qu'il n'y a pas son type devant on ne sait même pas s'il s'agit réellement du chiffre 1 mais en tout cas ça le suggère.
    Quand on ne met pas de type devant des valeurs, on regarde leurs écritures. Avec des quotes simples c'est un caractère (donc le nombre correspondant à sa valeur ascii). Sans quote c'est le nombre tel qu'il est écrit. S'il est précédé d'un "0" (ex 045) alors c'est un nombre en base 8 (donc ici 37). S'il est précédé d'un "0x" (ex 0x45) c'est un nombre en base 16 (donc ici 69).

    Citation Envoyé par zephyre Voir le message
    Oui je l'ai déjà fait en me basant sur la table ASCII pour les entiers strictement positif en suivant les cours de Jacque Olivier Lapeyre sur YouTube.
    Donc tu comprends alors que '1' et 49 c'est la même chose (mais c'est plus parlant d'écrire '1' que 49)

    Citation Envoyé par zephyre Voir le message
    Mais s'il reste des trucs à récupérer pourquoi il les affiches pas alors vu que je fais un printf juste derrière ?
    Mais il les affiche (et tu vois je ne dis rien sur le "s" en trop). Mais il affiche ce qui reste passé par strtod(). Or quand tu as entré un prénom (donc à priori des lettres) plus long que ce que fgets() récupère, ce qui reste dans le clavier ce sont toujours des lettres. Et faire passer des lettres au travers de strtod() ça rend pas terrible au final.

    Citation Envoyé par zephyre Voir le message
    Oui tu as raison, purée c'est vraiment la mouise !
    J'avais jamais pensé à ça !
    Rien ne t'interdit de supprimer le '\n' ; te suffit de le remplacer par un '\0'. Et comme c'est forcément le dernier caractère de ta saisie, cela ne gêne en rien. En fait c'est même assez souvent préférable surtout quand tu compares une info saisie (ou lue dans un fichier) avec une info attendue. Car quoi que tu fasses, "toto\n" ne sera jamais égal à "toto".

    Citation Envoyé par zephyre Voir le message
    Je crois que -Eks- a donné la solution avec son idée de purger le tampon.
    Ce n'est pas vraiment "son" idée, le truc étant déjà bien ancré par la communauté. Mais ce n'est pas non plus toujours la solution miracle. On associe souvent stdin au clavier. Ok c'est généralement le cas, mais pas toujours. Dans les systèmes types Unix, les programmes peuvent communiquer les uns aux autres au travers de tubes de communication (appelés "pipes" du fait de leur traduction américaine).
    Quand un tube est mis en place entre deux programmes P1 et P2, la sortie de P1 (stdout) devient l'entrée de P2 (stdin). A ce moment là le clavier est shunté et tout ce que lit P2 est pris dans ce que P1 lui envoie. Le code de P2 ne change pas. Si P2 fait un fgets(..., ..., stdin), le fgets() est bien exécuté sauf que ce qui est lu provient de ce que P1 a écrit.
    Petit exemple: un programme "lire.c"
    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include <stdio.h>
    #include <string.h>
     
    #define SZ_STR		(10)
     
    int main(/* pas void car c'est une notation C++ et non C */) {
    	// Saisie nom, suppression du '\n'
    	char nom[SZ_STR + 1];
    	fputs("Quel est ton nom ?", stdout); fflush(stdout);
    	fgets(nom, SZ_STR + 1, stdin);
    	nom[strcspn(nom, "\n")]='\0';
     
    	// Saisie prénom, suppression du '\n'
    	char prenom[SZ_STR + 1];
    	fputs("Quel est ton prénom ?", stdout); fflush(stdout);
    	fgets(prenom, SZ_STR + 1, stdin);
    	prenom[strcspn(prenom, "\n")]='\0';
     
    	// Affichage informations lues
    	printf("Ton nom est  : [%s]\n", nom);
    	printf("Ton prenom est  : [%s]\n", prenom);
    	return 0;
    }
    Là déjà tu peux le tester en tapant plus de 10 lettres à la première question, tu verras réellement le reste apparaitre après la seconde question qui ne t'aura pas laissé le temps d'y répondre. Et si 10 ne suffit pas, comme il a été mis en macro, si demain tu veux passer à 20 tu n'as qu'une seule modification à faire (le "+1" étant une indication pour les autres programmeurs montrant que je n'ai pas oublié la place pour le '\0' et le printf(), peu utile quand on ne veut afficher que des strings figées, étant avantageusement remplacé par fputs() plus rapide car moins complexe).

    Un second programme "ecrire.c"
    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    #include <stdio.h>
     
    int main() {
    	fputs("toto\n", stdout);
    	fputs("titi\n", stdout);
     
    	return 0;
    }
    Si t'es (ou que tu peux disposer) d'un Linux, tu compiles ces deux programmes en "ecrire" et "lire" puis tu tapes la commande ./ecrire | ./lire et tu verras "toto" et "titi" apparaitre en réponse aux questions posées par "lire" sans que tu aies eu à taper quoi que ce soit. Le caractère "|" symbolisant le pipe s'obtient via en appuyant sur "alt-gr" puis sur la touche "6".
    Accessoirement c'est mieux d'avoir un Linux quand on veut apprendre le C car les systèmes type Unix ayant été écrits en C, le C leur est naturellement intégré. Il semble que Windows connaisse le pipe mais il s'agit d'un ersatz limité à un pipe P1 | P2 (Unix, lui, peut chainer les pipes P1 | P2 | P3 | ... à l'infini) et je n'ai jamais regardé s'il était capable de tout piper (de toute façon je ne fais pas de C sous Windows).

    Donc pour en revenir à la puge, si maintenant ton programme s'amuse à purger stdin parce qu'il n'est pas capable de tout lire, il perd des infos peut-être importante. Les pipes dans Unix permettent d'analyser des logs, de faire des stats, etc. Toute l'administration d'Unix étant basée sur les fichiers textes, on passe le fichier à P1 on peut extraire une info X, on le passe à P2 on peut en extraire une info Y. Une fois j'ai codé un outil convertissant un CSV en fichier LDAP en utilisant 8 outils Unix différents tous pipés les uns sur les autres (le premier outil faisant ceci, le second continuant en cela et etc).
    Maintenant imagine qu'on soit sur un système bancaire (ou une centrale nucléaire), le système a été hacké, on passe le log des connexions à ton programme pour trouver l'adresse IP des hackeurs et ton programme en purge la moitié...

    Si tu dois lire un truc, effectivement fgets() c'est mieux que scanf() car scanf() il demande de la précision et ce que tape quelqu'un c'est tout sauf précis. Contrairement à ce qu'on apprend dans les écoles, scanf() n'a pas été fait pour faire de la saisie mais comme c'est une fonction assez générale (on peut faire saisir tout type par le biais des "%") c'est généralement la première qu'on apprend aux élèves car elle "semble" plus simple mais malheureusement on ne peut que masquer tout ce qu'elle entraine derrière comme soucis car le débutant n'a pas encore l'expérience pour les comprendre.
    Donc si tu dois lire une ligne de stdin, il faut que tu lises toute la ligne entière pour 1) ne rien perdre et 2) avoir un stdin toujours clean. Bien entendu on peut pas te demander de "deviner" la taille maximale d'une ligne donc généralement on prend des tailles assez grandes pour admettre que tout y rentrera comme par exemple un char[1000 + 1] (on admet tous que les lignes d'un fichier texte ne feront jamais 1000 caractères et on n'oublie pas "+1" pour là encore montrer aux autres qu'on a bien pensé au '\0').

    Et si vraiment on veut un truc ultra blindé, alors il existe la fonction getline() qui elle lit une ligne de taille potentiellement infinie et qui alloue la mémoire suffisante pour la stocker dans un pointeur mémoire (pour plus tard quand tu auras vu les pointeurs)
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  15. #15
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    Donc pour en revenir à la puge, si maintenant ton programme s'amuse à purger stdin parce qu'il n'est pas capable de tout lire, il perd des infos peut-être importante. Les pipes dans Unix permettent d'analyser des logs, de faire des stats, etc. (...)
    Tu compares des programmes Unix conçus pour fonctionner de façon chaînée avec une entrée redirigée, du type filtres grep ou sed fonctionnant avec cat ou autres, avec un programme conçu pour valider une saisie au clavier par un humain auquel on peut demander de retaper quelque chose qui ne respecterait pas certains critères, de taille par exemple, c'est à dire pouvant donner une chance à l'utilisateur de corriger sa saisie (la saisie erronée est de toutes façons ignorée et on demande à l'utilisateur de taper de nouveau, donc rien n'est "perdu").

    Il me semble que ce sont deux types de comportements de programmes exclusifs l'un de l'autre.

    Le programme non interactif pourra être chaîné, pourra être intégré dans des scripts, exécuté automatiquement à heures fixes, etc., mais s'il doit vérifier des contraintes et qu'elles échouent, il ne pourra rien faire d'autre sans intervention humaine que s'arrêter ou ignorer les données et afficher ou loguer une erreur ou un avertissement selon la façon dont il est conçu.

    Citation Envoyé par Sve@r Voir le message
    Si tu dois lire un truc, effectivement fgets() c'est mieux que scanf() car scanf() il demande de la précision et ce que tape quelqu'un c'est tout sauf précis. Contrairement à ce qu'on apprend dans les écoles, scanf() n'a pas été fait pour faire de la saisie mais comme c'est une fonction assez générale (on peut faire saisir tout type par le biais des "%") c'est généralement la première qu'on apprend aux élèves car elle "semble" plus simple mais malheureusement on ne peut que masquer tout ce qu'elle entraine derrière comme soucis car le débutant n'a pas encore l'expérience pour les comprendre.
    Donc si tu dois lire une ligne de stdin, il faut que tu lises toute la ligne entière pour 1) ne rien perdre et 2) avoir un stdin toujours clean. Bien entendu on peut pas te demander de "deviner" la taille maximale d'une ligne donc généralement on prend des tailles assez grandes pour admettre que tout y rentrera comme par exemple un char[1000 + 1] (on admet tous que les lignes d'un fichier texte ne feront jamais 1000 caractères et on n'oublie pas "+1" pour là encore montrer aux autres qu'on a bien pensé au '\0').

    Et si vraiment on veut un truc ultra blindé, alors il existe la fonction getline() qui elle lit une ligne de taille potentiellement infinie et qui alloue la mémoire suffisante pour la stocker dans un pointeur mémoire (pour plus tard quand tu auras vu les pointeurs)
    On peut aussi restreindre le nombre de char lus avec scanf() :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    		char ch[50]= { '\0' };
    		int c;
     
    		printf("Tapez 49 char au plus\n");
    		if (scanf("%49[^\n]", ch) == 1) {
    			printf("J'ai récupéré : [%s]\n", ch);
    			/* \n n'est pas dans les caractères capturés, et reste dans
    			 * le flux, qu'il faut donc purger si on ne veut pas qu'il soit lu par
    			 * un prochain scanf. */
    			while ((c = getchar()) != '\n' && c != EOF)
    				/* discard rest of line */ ;
    		}
    fgets() a son lot de complexités. Le fait de considérer comme acquis que les lignes ne dépasseront pas 1000 char est une solutions palliative, qui provoquera des résultats inattendus, ou pire, quand cela arrivera. Cela dit, cela peut-être une solution de compromis dans le cadre d'un exercice.

    getline() est très bien (ou une bibliothèque comme readline ou carrément ncurses si on fait une TUI), plutôt que les bricolages auxquels on est réduits avec scanf() et fgets() pour gérer une entrée au clavier. getline() n'est cependant pas une fonction de la bibliothèque standard du C. On la trouve sur les systèmes conformes au standard POSIX et pas sous Windows.

  16. #16
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 437
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 437
    Points : 43 078
    Points
    43 078
    Par défaut
    Tu en demandes 9, tu en entres 150, la fonction n'en récupère quand-même que 9 (ou plutôt 8 car elle garde 1 place pour le '\0') et laisse le reste dans le buffer prêt à être récupéré au tour suivant.
    Je pense que l'explication est assez claire.


    Tu compares des programmes Unix conçus pour fonctionner de façon chaînée avec une entrée redirigée, du type filtres grep ou sed fonctionnant avec cat ou autres, avec un programme conçu pour valider une saisie au clavier par un humain
    Sous Unix/Linux tout est fichier, et notamment stdin. La fonction fgets travaille sur des fichiers comme l'indique son prototype. stdin reste un fichier, même si c'est un fichier "spécial" qui n'est qu'en lecture seule. Le programme ne fait pas de lecture clavier, mais une lecture dans le pseudo fichier stdin.

    Quand fgets lit un fichier, il s'arrête au 1er retour chariot ou à la limite de son buffer, cela ne signifie pas que la fin du fichier soit atteinte, une seconde lecture fera de même. avec stdin, tu ne peux pas atteindre EOF, par contre par exemple, tu peux fermer le flux avec fclose ce qui empêchera ton processus de faire une lecture sur stdin.

    Si tu veux être sûr qu'il n'y ai rien en attente après une lecture stdin, c'est à toi de vider le buffer.
    un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    fseek(stdin, 0, SEEK_END);
    devrait fonctionner.

    Ce qui peut être chiant avec fgets, c'est de devoir éliminer le retour chariot à la fin par exemple (pas compliqué), mais il faut garder à l'esprit l'usage de la fonction : lire des lignes d'un fichier.


    après il existe d'autres fonctions pour mieux gérer cela.
    getline est intéressante car va lire une ligne entière en gérant l'allocation, mais a par contre l'inconvénient (si on considère que s'en est un) de laisser à la charge du développeur la libération de celui-ci. getline aura le même comportement que fgets avec \n.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  17. #17
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par -Eks- Voir le message
    Tu compares des programmes Unix conçus pour fonctionner de façon chaînée avec une entrée redirigée, du type filtres grep ou sed fonctionnant avec cat ou autres, avec un programme conçu pour valider une saisie au clavier par un humain auquel on peut demander de retaper quelque chose qui ne respecterait pas certains critères, de taille par exemple, c'est à dire pouvant donner une chance à l'utilisateur de corriger sa saisie (la saisie erronée est de toutes façons ignorée et on demande à l'utilisateur de taper de nouveau, donc rien n'est "perdu").

    Il me semble que ce sont deux types de comportements de programmes exclusifs l'un de l'autre.
    Oui bien entendu. C'était pour montrer une autre approche possible du traitement de stdin. Accessoirement le C ne me semble pas le langage le plus adéquat pour un programme qui pose des questions et qui gère les réponses données. Il peut le faire (il est "Turing complet") mais on galère sec comparativement à d'autres langages comme le shell ou Python...

    Citation Envoyé par -Eks- Voir le message
    Le fait de considérer comme acquis que les lignes ne dépasseront pas 1000 char est une solutions palliative, qui provoquera des résultats inattendus, ou pire, quand cela arrivera. Cela dit, cela peut-être une solution de compromis dans le cadre d'un exercice.
    Il faut mettre en balance le ratio "criticité/coût de développement". Si je dois créer un analyseur de log, partir du postulat que chaque ligne ne fera pas plus de 1000c me semble une hypothèse acceptable (d'autant plus si l'origine du log est connue et maîtrisée). Par ailleurs je mettrais ça dans une macro (cf mon exemple "lire.c" du post précédent) afin de pouvoir monter facilement si besoin.

    Citation Envoyé par chrtophe Voir le message
    Ce qui peut être chiant avec fgets, c'est de devoir éliminer le retour chariot à la fin par exemple (pas compliqué)
    Ca prend une ligne: buffer[strcspn(buffer, "\n")]='\0'.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  18. #18
    Membre averti
    Homme Profil pro
    très occupé
    Inscrit en
    Juillet 2014
    Messages
    137
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : très occupé

    Informations forums :
    Inscription : Juillet 2014
    Messages : 137
    Points : 411
    Points
    411
    Par défaut
    Citation Envoyé par chrtophe Voir le message
    Sous Unix/Linux tout est fichier, et notamment stdin. La fonction fgets travaille sur des fichiers comme l'indique son prototype. stdin reste un fichier, même si c'est un fichier "spécial" qui n'est qu'en lecture seule. Le programme ne fait pas de lecture clavier, mais une lecture dans le pseudo fichier stdin.

    Quand fgets lit un fichier, il s'arrête au 1er retour chariot ou à la limite de son buffer, cela ne signifie pas que la fin du fichier soit atteinte, une seconde lecture fera de même. avec stdin, tu ne peux pas atteindre EOF, par contre par exemple, tu peux fermer le flux avec fclose ce qui empêchera ton processus de faire une lecture sur stdin.

    Si tu veux être sûr qu'il n'y ai rien en attente après une lecture stdin, c'est à toi de vider le buffer.
    un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    fseek(stdin, 0, SEEK_END);
    devrait fonctionner.
    Ce "fichier" est tellement spécial, que ce fseek ne fonctionne pas chez moi (test effectué sous Linux Debian avec gcc 8.3.0) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include <stdio.h>
     
    int main(void) {
     
            char str1[9] = { '\0' };
            char str2[9] = { '\0' };
     
            printf("saisissez plus de 8 char :\n");
            fgets(str1, 9, stdin);
            int ret = fseek(stdin, 0L, SEEK_END);
            if (ret == -1)
                    perror("fseek a échoué ");
            fgets(str2, 9, stdin);
     
            printf("str1 = [%s]\n", str1);
            printf("str2 = [%s]\n", str2);
     
            return 0;
    }
    donne :

    $ gcc -Wall -Wextra 11853755.c
    $ ./a.out
    saisissez plus de 8 char :
    Philippe, roi des Belges
    fseek a échoué : Illegal seek
    str1 = [Philippe]
    str2 = [, roi de]
    Je ne connais pas d'autre façon portable de vider le tampon de stdin que de consommer son contenu avec un code du type de celui que j'ai posté.

  19. #19
    Responsable Systèmes


    Homme Profil pro
    Gestion de parcs informatique
    Inscrit en
    Août 2011
    Messages
    17 437
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Gestion de parcs informatique
    Secteur : High Tech - Matériel informatique

    Informations forums :
    Inscription : Août 2011
    Messages : 17 437
    Points : 43 078
    Points
    43 078
    Par défaut
    Ce "fichier" est tellement spécial, que ce fseek ne fonctionne pas chez moi (test effectué sous Linux Debian avec gcc 8.3.0)
    J'ai essayé fseek, et effectivement ça ne fonctionne pas, aucune info sur ceci dans le man de fseek, il aurait été pertinent qu’il y ai une précision là-dessus.
    La méthode de lecture avec getchar/getc/fgetc fonctionne, je ne l'ai jamais contesté. Que fseek fonctionne aurait permit de n'avoir qu'une ligne de code dans le source.
    Ma page sur developpez.com : http://chrtophe.developpez.com/ (avec mes articles)
    Mon article sur le P2V, mon article sur le cloud
    Consultez nos FAQ : Windows, Linux, Virtualisation

  20. #20
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 685
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 685
    Points : 30 974
    Points
    30 974
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par chrtophe Voir le message
    aucune info sur ceci dans le man de fseek, il aurait été pertinent qu’il y ai une précision là-dessus.
    C'est vrai, le man ne dit rien à ce propos.
    On en parle ici: https://stackoverflow.com/questions/...oints-to-stdin. Il y est dit que fseek() ne fonctionne que sur un fichier disque ou quelque chose d'équivalent. Personnellement je pense que "équivalent" doit signifier un support adressable et que fseek() ne fonctionnera donc par exemple pas non plus pour une bande magnétique...
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Problème de message d'erreur sur une fonction SNMP
    Par kriptoo dans le forum Langage
    Réponses: 1
    Dernier message: 18/05/2007, 01h08
  2. Erreur sur la fonction getdate()
    Par obydissonn dans le forum MS SQL Server
    Réponses: 3
    Dernier message: 25/04/2007, 11h48
  3. [MySQL] Erreur sur la fonction mysql_result()
    Par nico26 dans le forum PHP & Base de données
    Réponses: 5
    Dernier message: 31/01/2007, 15h50
  4. erreur sur une fonction
    Par rimbaut dans le forum C
    Réponses: 3
    Dernier message: 01/04/2006, 17h28
  5. Erreur sur une fonction avec des paramètres
    Par Elois dans le forum PostgreSQL
    Réponses: 2
    Dernier message: 05/05/2004, 21h00

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo