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 :

Entrez "quit" pour quitter ce : Comment faire ça sans bloquer dans la boucle ?


Sujet :

C

  1. #1
    Membre du Club
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    215
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 215
    Points : 60
    Points
    60
    Par défaut Entrez "quit" pour quitter ce : Comment faire ça sans bloquer dans la boucle ?
    Bonjour.

    J'ai un programme qui tourne pour un nombre d'itérations données.

    Je souhaiterais pouvoir l'arrêter avant d'atteindre la dernière itération en entrant "q" ou "quit" au clavier.

    Je précise que le programme n'attends pas et n'a pas besoin d'une entrée clavier d'une donnée quelconque de la part de l'utilisateur pour itérer.

    Le programme fait son travail, et quand je décide de l'arrêter prématurément, je rentre "q" au clavier, c'est tout !

    Or, tout les exemples que je trouve sont basés sur un code qui demande une réponse de l’utilisateur pour pouvoir itérer ! Le code bloque en attendant une réponse. Or je ne veux pas ça !

    Exemple de code essayé qui ne correspond pas !
    Source du code ici
    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
    #include <stdio.h>
    int main()
    {
        char ch;
        puts("Typing Program");
        puts("Type away; press 'q' to quit:");
        for(;;)
        {
            ch=getchar();
            if(ch=='~')
            {
                break;
            }
        }
        return(0);
    }
    Impossible de trouver ce que je souhaite !

  2. #2
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Il n'y a que deux solutions:
    demander à l'utilisateur d'interrompre le programme lui-même (avec CTRL+C sous linux, ou l'équivalent de windows).
    utiliser un thread supplémentaire.

    Dans l'idée du thread, il faut initialiser une variable dédiée (par exemple finDemandee) avant d'itérer, et vérifier si sa valeur a changé à chaque itération.
    Dans un thread, on fait une boucle, donc bloquant ce thread uniquement, pour demander q ou quit. Lorsque la réponse est satisfaisante, on modifie finDemandee puis on termine le thread.

    Ceci provoquera la fin du programme lorsque la boucle principale passera à l'itération suivante.

    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
     
    void attendre_quitter(int * terminer) {
        puts("Typing Program");
        puts("Type away; press 'q' to quit:");
        while (getchar()!='~);
        *terminer = 1;
    }
     
    int main() {
        int terminer = 0;
        //du code pour appeler "attendre_quitter(&terminer)" dans un autre thread;
        while(!terminer) {
            calculer_un_peu_plus()
        }
        return 0;
    }
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  3. #3
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Tu veux de l'interactivité en ligne de commande : il n'existe pas de solution portable à ton problème sans recourir à des bibliothèques tierces.

    Trois axes de réflexion :

    • passer en mode non canonique pour la durée d'exécution du programme, avec toutes les conséquences que cela implique sur le comportement et la portabilité du programme ;
    • exploiter un système qui va exécuter le traitement et intercepter les évènements de manière asynchrone, comme par exemple une bibliothèque évènementielle telle libuv (overkill ici, AMHA) ;
    • prendre en charge les signaux et plus particulièrement SIGTERM que l'utilisateur peut envoyer via Ctrl-C .

    Dans le cas que tu évoques, ce dernier point a ma préférence personnelle et est sans doute la best practice, au moins sur les systèmes Posix-compliant. C'est plus générique et ça évite au programme de faire de l'entrée-sortie qui le détourne de sa tâche principale.

  4. #4
    Membre du Club
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    215
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 215
    Points : 60
    Points
    60
    Par défaut
    Gloups !
    Je suis débutant !

    La solution CTRL + C ne me convient pas car, en fait, je souhaiterais avoir plusieurs options pour quitter le programme.

    La solution de ternel semblait accessible, à part :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    //du code pour appeler "attendre_quitter(&terminer)" dans un autre thread;
    Sinon, je me fais la main sur une Raspberry pi, mais après, ce serait pour faire tourner sur une machine windows. Comme vous faisiez référence à la notion de portabilité .....

  5. #5
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    Et bien, tu en as de la chance

    le Raspberry PI utilise généralement un linux, donc l'API posix, tandis que Windows fait à sa sauce.
    Conclusion, utiliser les apis manuellement n'est pas portable.
    Tu peux t'en sortir avec les threads de C11. (si c'est supporté sur le PI)

    Cela dit, je confirme et conseille la réponse de Matt_Houston: intercepte le sigterm, et en guise de signal, utilise une fonction qui modifie la variable de controle.
    C'est sensiblement équivalent au thread, mais sans thread, et c'est portable.
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 369
    Points : 23 623
    Points
    23 623
    Par défaut
    Bonjour,

    Citation Envoyé par ternel Voir le message
    Il n'y a que deux solutions:
    demander à l'utilisateur d'interrompre le programme lui-même (avec CTRL+C sous linux, ou l'équivalent de windows).
    utiliser un thread supplémentaire.
    Euh… non ! On ne peut pas te laisser dire cela. Certes, utiliser un thread est une pratique courante — aujourd'hui — quand ceux-ci sont disponibles et que l'on utilise une interface utilisateur sophistiquée, ce qui lui permet d'être réactive même si des travaux asynchrones fonctionnent en arrière-plan. Mais il faut être capable de le faire sur toutes sortes de machines, surtout s'il s'agit simplement de saisir l'état d'une touche pour sortir. Si ce n'était pas possible sans thread, c'est l'informatique grand public entière qui n'aurait démarré qu'au milieu des années 2000.

    Citation Envoyé par hary66 Voir le message
    La solution CTRL + C ne me convient pas car, en fait, je souhaiterais avoir plusieurs options pour quitter le programme.

    La solution de ternel semblait accessible, à part :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    //du code pour appeler "attendre_quitter(&terminer)" dans un autre thread;
    Sinon, je me fais la main sur une Raspberry pi, mais après, ce serait pour faire tourner sur une machine windows. Comme vous faisiez référence à la notion de portabilité .....
    Dans ce cas-là, n'utilise pas « getch() » sans savoir exactement à quelle interface tu fais appel. L'appel POSIX est déprécié s'il n'a pas d'underscore « _ » initial. Ensuite, cet appel ne fait PAS partie du C standard. Il est défini soit par conio.h qui est spécifique à Windows, soit par curses ou ncurses qui est une bibliothèque de plus haut niveau et qui prend en charge la gestion de la saisie.

    Si tu t'appuies sur ncurses, tu peux tout de suite passer en mode non bloquant avec nodelay(). L'appel à getch() renvoie alors ERR s'il n'y a pas de caractère disponible. Extrait de la man page concernée :

    Citation Envoyé par man getch
    DESCRIPTION
    Reading characters
    The getch, wgetch, mvgetch and mvwgetch, routines read a character from the window. In no-delay mode, if no input is waiting, the value ERR is returned. In delay mode, the
    program waits until the system passes text through to the program.
    Mais sinon, en s'appuyant au moins sur C et sur POSIX uniquement, il faut soit passer l'entrée standard en mode non-bloquant grâce à fcntl() puis laisser getchar() (et pas « getch() ») renvoyer EOF, soit passer par select() pour monitorer un ensemble de flux.

    Fais une recherche avec select() sur ce forum car le sujet a été maintes fois disserté ici, et parce que ça permet d'éviter de faire une attente active consommant le temps processeur pour rien si jamais il n'y a rien à traiter.

  7. #7
    Membre du Club
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    215
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 215
    Points : 60
    Points
    60
    Par défaut
    Comme je l'ai dit plus haut, je débute en programmation !
    J'ai l'impression que ça fait beaucoup de notion d'un coup pour moi !

  8. #8
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 369
    Points : 23 623
    Points
    23 623
    Par défaut
    Citation Envoyé par hary66 Voir le message
    Comme je l'ai dit plus haut, je débute en programmation !
    J'ai l'impression que ça fait beaucoup de notion d'un coup pour moi !
    Pour faire court :

    • getch(), c'est spécifique Windows. Enfin, ça l'est quand tu utilises conio.h, parce que ncurses a décidé de nommer un de ses appels de la même façon et de proposer en plus des services qui correspondraient aussi à ce que tu cherches, mais il y a 99 % de chances que ton programme ne s'appuie pas dessus si tu ne lui as pas demandé explicitement de le faire. Si tu débutes en C et que tu veux lire un caractère, utilise getchar() qui, lui, fait partie de la norme C ;
    • En C (ainsi qu'avec pas mal d'autres langages ET systèmes d'exploitation ayant suivi le même modèle), lorsque tu fais une opération de lecture sans préciser la source, celle-ci est par défaut « l'entrée standard », et cette entrée standard est elle-même, par défaut, reliée au clavier de la console de l'utilisateur qui a lancé ton programme MAIS ce n'est pas une obligation : cela peut être la sortie d'un autre programme si l'utilisateur a utilisé un pipe « | » ou une connexion à distance par modem. Il se peut même que cela ne soit rien du tout ;
    • Ton programme gère l'entrée standard comme s'il s'agissait d'un fichier et il attend sagement que le prochain caractère arrive si le système ne lui répond pas carrément qu'il en a atteint la fin. Avec un clavier, on n'atteint jamais cette fin puisque qu'il est toujours possible de saisir une touche après la précédente ;
    • Si tu prends l'exemple de ton Raspberry Pi, celui-ci est fait pour fonctionner tout seul par défaut : il n'y a ni écran ni clavier connecté dessus. Dans ces conditions, ton programme ne pourrait pas être exécuté s'il dépendait directement d'un clavier.


    Ceci veut dire que gérer le clavier efficacement relève de la programmation système : il faut demander à ton OS d'aller paramétrer la gestion d'un périphérique en particulier, en l'occurrence le clavier, ou LES claviers car aujourd'hui, non seulement il y a plusieurs périphériques de saisie, mais même un truc aussi élémentaire que le clavier peut provenir de sources différentes : port PS/2 ou USB, par exemple. Donc, généralement, tous ces périphériques sont réunis en une seule classe et il y a une couche d'abstraction au-dessus pour faire croire qu'il s'agit d'une et unique source. C'est à ce niveau-là qu'il faut intervenir.

    Donc, concrètement, les approches qui s'offrent à toi aujourd'hui, sont :

    • Utiliser kbhit() (littéralement « frappe clavier ») qui est un appel non-bloquant et qui t'informe qu'on a appuyé sur le clavier. Donc, un caractère (hors touches spéciales) a bien été saisi et tu pourras le récupérer avec getchar() ou getch() sans risque de blocage MAIS c'est là encore spécifique à Windows, et non seulement ça ne fonctionnera que sur les machines qui ont un clavier, mais cela t'empêchera de chaîner ton programme avec « | » (il continuerait à attendre bêtement le clavier alors que les caractères proviendraient d'une autre source). Donc à oublier ;
    • Soit tu informes le système que tu lis bien l'entrée standard (quelque soit la provenance des caractères à lire) mais que tu ne souhaites pas que les appels soient bloquants, ce qui est en fait exactement l'énoncé de ta demande initiale : donc tu fais un appel en entrée de programme pour modifier les paramètres d'un flux de fichier (ici l'entrée standard) avec fcntl() et tu lui passes le flag O_NONBLOCK. À noter que ceci provient des systèmes Unix et donc aujourd'hui de la norme POSIX, reconnue par Windows, mais pas de la norme C. C'est donc bien de la programmation système ;
    • Soit encore, s'appuyer sur les systèmes de signaux comme proposé plus haut pour faire complètement abstraction de la gestion de l'entrée utilisateur et laisser les mécanismes systèmes traditionnels prendre cela en charge. Tu peux mettre en place un « gestionnaire de signal » avec signal() ou sigaction() pour qu'une certaine procédure soit automatiquement exécutée à réception dudit signal. Tu peux te servir de ce handler pour changer l'état d'un flag qui serait lu depuis ton programme principal, qui lui proposerait le menu de sortie s'il voit qu'il a changé, à la manière de finDemandee proposé par ternel. C'est propre, mais la tentation d'utiliser une variable globale pour implémenter ce flag est grande, et c'est là encore une pratique que l'on essaie de faire éviter aux débutants. Par ailleurs, il faut bien faire attention au type de signal reçu et à l'instance qui les a envoyés. Si c'est l'utilisateur, ça ne pose pas de problème mais si c'est le système qui l'a fait parce que l'ordinateur est en train de s'éteindre, par exemple, alors il vaut mieux quitter silencieusement et ne pas interroger l'utilisateur (qui ne serait plus là pour répondre) ;
    • Soit utiliser poll() ou select() pour vérifier si un ou plusieurs sont bloquants à un moment donné. C'est LA voie à suivre lorsque l'on veut suivre plusieurs flux à la fois, par exemple, si l'on gère un serveur ;
    • Soit décider d'utiliser ncurses et d'invoquer ses propres appels pour gérer cela à ta place (mais ça ne fait que déplacer le problème) ;
    • Soit construire toute ton application sur une bibliothèque d'usage général et de plus haut niveau, comme la SDL.


    kbhit() est vraisemblablement ce qui va te tirer d'affaire pour ce cas précis, mais considère que tu n'auras plus jamais à y recourir par la suite. Si c'est quand même le cas, c'est que ton programme est mal conçu.

    Sinon, select et poll sont intéressants même pour surveiller un seul flux car il permettent de spécifier une valeur de timeout qui peut éventuellement être nulle (donc rendre la main immédiatement) et parce que cela permet de faire des opérations sans avoir à changer explicitement les flags du flux concerné et d'avoir à les remettre en place ensuite.

    Sache enfin que sous Unix et assimilés, les applications « consoles » sont en fait exploitées à travers des « terminaux virtuels » (la console proprement dite ou les xterm de X-Window). Ces terminaux fonctionnent en mode canonique par défaut, ce qui signifie qu'ils sont faits pour bufferiser la ligne que saisit l'utilisateur et lui permettre de la corriger, jusqu'à ce qu'il appuie sur Entrée, moment où le contenu du buffer est envoyé au processus concerné. Ça veut dire que tu ne recevrais les caractères qu'après avoir tapé Entrée. Si tu veux avoir une réception immédiate, il faudra se pencher sur termios où, là encore, laisser ncurses gérer tout cela pour toi.

  9. #9
    Expert éminent
    Avatar de fred1599
    Homme Profil pro
    Lead Dev Python
    Inscrit en
    Juillet 2006
    Messages
    3 817
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Meurthe et Moselle (Lorraine)

    Informations professionnelles :
    Activité : Lead Dev Python
    Secteur : Arts - Culture

    Informations forums :
    Inscription : Juillet 2006
    Messages : 3 817
    Points : 7 110
    Points
    7 110
    Par défaut
    Citation Envoyé par hary66 Voir le message
    Comme je l'ai dit plus haut, je débute en programmation !
    J'ai l'impression que ça fait beaucoup de notion d'un coup pour moi !
    Ouais mais nous on va pas faire le taf pour toi, alors comment on fait pour résoudre cette problématique ?
    Celui qui trouve sans chercher est celui qui a longtemps cherché sans trouver.(Bachelard)
    La connaissance s'acquiert par l'expérience, tout le reste n'est que de l'information.(Einstein)

  10. #10
    Membre du Club
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    215
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 215
    Points : 60
    Points
    60
    Par défaut
    J'ai tenté le coup avec kbhit(), _kbhit() plus exactement car kbhit() ne "passait" pas !

    Par contre comme j'ai une boucle qui fait tourner d'autres boucles, je doit mettre tout ça dans toutes les sous boucles ! Ca fait fouilli à la fin.

    Mais là, j'ai un peu une indigestion à cause des réponses très copieuses ! Tout ça parait très intéressant, mais je ne peux en exploiter le 10ème.

    J'ai tenté de trouver de la doc sur fcntl(), mais je ne trouve que des choses liées à Unix, et je ne comprends pas bien si c'est encore du C ?

  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
    Bonjour
    Citation Envoyé par hary66 Voir le message
    Par contre comme j'ai une boucle qui fait tourner d'autres boucles, je dois mettre tout ça dans toutes les sous boucles ! Ca fait fouilli à la fin.
    Ben oui. C'est pour ça qu'il te faut apprendre à découper ton code en tâches élémentaires...

    Citation Envoyé par hary66 Voir le message
    J'ai tenté de trouver de la doc sur fcntl(), mais je ne trouve que des choses liées à Unix, et je ne comprends pas bien si c'est encore du C ?
    Unix a été écrit en C. Il t'est donc proposé un ensemble de fonctions te permettant de créer tes propres outils qui pourront s'interfacer avec l'OS. Ce sont des fonctions C donc c'est encore du C.
    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
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    215
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 215
    Points : 60
    Points
    60
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    [*]Soit encore, s'appuyer sur les systèmes de signaux comme proposé plus haut pour faire complètement abstraction de la gestion de l'entrée utilisateur et laisser les mécanismes systèmes traditionnels prendre cela en charge. Tu peux mettre en place un « gestionnaire de signal » avec signal() ou sigaction() pour qu'une certaine procédure soit automatiquement exécutée à réception dudit signal. Tu peux te servir de ce handler pour changer l'état d'un flag qui serait lu depuis ton programme principal, qui lui proposerait le menu de sortie s'il voit qu'il a changé, à la manière de finDemandee proposé par ternel. C'est propre, mais la tentation d'utiliser une variable globale pour implémenter ce flag est grande, et c'est là encore une pratique que l'on essaie de faire éviter aux débutants. Par ailleurs, il faut bien faire attention au type de signal reçu et à l'instance qui les a envoyés. Si c'est l'utilisateur, ça ne pose pas de problème mais si c'est le système qui l'a fait parce que l'ordinateur est en train de s'éteindre, par exemple, alors il vaut mieux quitter silencieusement et ne pas interroger l'utilisateur (qui ne serait plus là pour répondre) ;
    i.

    J'ai regardé du coté de signal(), mais on ne peut intercepter que Ctrl-C et Ctrl-Z si j'ai bien compris, et il faut détourner l'une de ces interceptions vers une autre fonction fabriquée sur mesure.
    Tu parles de proposer un menu de sortie, mais ce menu ne bloquerait-il pas l’exécution du programme en attendant une réponse ?

  13. #13
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juin 2009
    Messages
    4 481
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 481
    Points : 13 679
    Points
    13 679
    Billets dans le blog
    1
    Par défaut
    C'est parce que signal() capte les signaux, et que certaines combinaisons de touches, comme celles que tu donnes, émettent des signaux. Un appui sur une touche quelconque ne veut pas dire émission d'un signal

  14. #14
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 369
    Points : 23 623
    Points
    23 623
    Par défaut
    Citation Envoyé par hary66 Voir le message
    J'ai regardé du coté de signal(), mais on ne peut intercepter que Ctrl-C et Ctrl-Z si j'ai bien compris, et il faut détourner l'une de ces interceptions vers une autre fonction fabriquée sur mesure.
    Citation Envoyé par Bktero Voir le message
    C'est parce que signal() capte les signaux, et que certaines combinaisons de touches, comme celles que tu donnes, émettent des signaux. Un appui sur une touche quelconque ne veut pas dire émission d'un signal
    Plus précisément, Ctrl+C est la combinaison de touches consacrée par défaut — depuis un shell — pour demander l'interruption du processus à l'avant-plan. Tu peux saisir stty -a dans un shell UNIX pour connaître les paramètres en cours du terminal, et notamment ces combinaisons. Le shell provoque ensuite l'interruption du processus en lui envoyant SIGINT qui, comme la quasi-totalité des signaux, tue le processus s'il n'est pas explicitement intercepté.

    Ça veut dire que ce n'est pas en soi une manière de gérer le clavier, mais une façon d'utiliser le dispositif standard d'interruption des processus quand il n'en sont pas pourvus, et d'une manière générale, la manière officielle d'utiliser le système d'exploitation. Note que Ctrl+C est une combinaison historique (dont on avait disserté l'origine sur ce forum, d'ailleurs, mais je ne me souviens plus où). En BASIC, par exemple (celui des années 80, pas le VB), les programmes étaient également interrompus de la même façon.

    Tu parles de proposer un menu de sortie, mais ce menu ne bloquerait-il pas l’exécution du programme en attendant une réponse ?
    C'est bien le sujet qui nous occupe depuis le départ, et c'est pour cela qu'il faut réfléchir à ce qui nous bloque, pourquoi ça nous bloque, ce dont on aurait besoin pour contourner la difficulté et… surtout : comment écrirait-on, nous, un programme ou un système d'exploitation qui permette de contourner ce problème.

    Réponse courte : retourne voir du côté de select(), comme proposé au départ.

    Mais avant ça, voici un programme simple MAIS à ne surtout pas prendre en exemple à moyen terme :

    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
    #include <stdio.h>
    #include <sys/types.h>
    #include <fcntl.h>
     
    int main (void)
    {
        int c;
        unsigned long int n=0;
        fcntl (0,F_SETFL,O_NONBLOCK);
     
        do
        {
            printf ("%16lu\r",n++);
            fflush (stdout);
     
            c = getchar();
        }
        while (c!='Q' && c!='q');
     
        return 0;
    }
    Ce programme fonctionne en permanence (voir le compteur défiler) mais te permet de sortir à tout moment en tapant q+Entrée. À part la touche Entrée surnuméraire, c'est bien ce que tu cherches à faire. D'où les questions suivantes :

    • Pourquoi est-ce que ça a l'air aussi simple alors que cela fait une semaine que l'on planche dessus ?
    • Pourquoi est-ce que ça marche alors que, depuis le départ, on bute sur le fait que getchar() soit justement bloquante ?
    • Pourquoi est-on obligé de valider avec Entrée ?
    • Pourquoi ai-je ajouté qu'il ne fallait surtout pas suivre cet exemple à moyen terme ?

  15. #15
    Membre confirmé
    Profil pro
    Inscrit en
    Mai 2011
    Messages
    252
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2011
    Messages : 252
    Points : 649
    Points
    649
    Par défaut Modèle évènementiel & SDL
    Je comprends ce que tu veux dire avec tes boucles dans des boucles. En fait il faut adopter un modèle de gestion des évènements comme dans le cas d'une interface graphique. Dans une seule boucle tu testes le clavier et exécutes des traitements en fonction des choix de l'utilisateur. Une espèce de menu comme tu l'as écrit d'ailleurs !

    Pour les réponses copieuses je te rejoins donc peut-être que tu es passé à côté de cette info :
    Citation Envoyé par Obsidian Voir le message
    Soit construire toute ton application sur une bibliothèque d'usage général et de plus haut niveau, comme la SDL.
    Cette biblio est légère, multi plates-formes, très simple à utiliser et te permettra de coder le modèle décrit. Tu trouveras toute la doc donc tu as besoin pour gérer le clavier en quelques lignes !

  16. #16
    Expert confirmé
    Inscrit en
    Mars 2005
    Messages
    1 431
    Détails du profil
    Informations forums :
    Inscription : Mars 2005
    Messages : 1 431
    Points : 4 182
    Points
    4 182
    Par défaut
    Attention à signal() qui est l'appel legacy, sigaction() est à lui préférer pour toute nouvelle application.

  17. #17
    Membre du Club
    Homme Profil pro
    Responsable de compte
    Inscrit en
    Juin 2014
    Messages
    215
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 54
    Localisation : France, Pyrénées Orientales (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Responsable de compte

    Informations forums :
    Inscription : Juin 2014
    Messages : 215
    Points : 60
    Points
    60
    Par défaut
    Citation Envoyé par Matt_Houston Voir le message
    Attention à signal() qui est l'appel legacy, sigaction() est à lui préférer pour toute nouvelle application.
    Oui, j'ai bien vu, mais les seuls petits exercices faciles à mettre en oeuvre que j'ai pu trouver concernaient signal().

    Citation Envoyé par Obsidian Voir le message

    Pourquoi est-ce que ça a l'air aussi simple alors que cela fait une semaine que l'on planche dessus ?
    Pourquoi est-ce que ça marche alors que, depuis le départ, on bute sur le fait que getchar() soit justement bloquante ?
    Pourquoi est-on obligé de valider avec Entrée ?
    Pourquoi ai-je ajouté qu'il ne fallait surtout pas suivre cet exemple à moyen terme ?
    Je sens un peu d'agacement dans la réponse. Désolé, mais je n'ai aucune réponse à ces questions.
    D'abord, je ne suis pas que sur ça. Ensuite, j'ai trouvé assez peu d'exemples "accessibles" concernant tes propositions. Je n'ai donc exploré les voies proposées que pour celle où on trouve des exemples de mise en œuvre. Tu vas me répondre qu'il y a la documentation, certes, mais avec une terminologie si spécifique et pointue et le plus souvent en Anglais, je suis bien souvent incapable de l'exploiter. Seul des petits exemples m'aide à la comprendre.
    Pour exemple concernant signal() :
    Certain peuvent être ignored, blocked, or handled. Va comprendre ces nuances sans exemple. Sans compter qu'il m'a été assez difficile de trouver des infos sur chaque signaux pour savoir lequel pouvait être ignored, blocked, or handled ! Idem pour trouver quel signal correspond à Ctrl-C ou Ctrl-Z, et je viens juste de découvrir qu'il y avait aussi un autre signal clavier Ctrl-\ (SIGQUIT).
    Je n'ai pas pu trouver sur une seule ressource toutes les infos regroupant tous les signaux avec le cas échéant leur commande clavier et indiquant s'il pouvait être ignored, blocked, or handled.

    Je passe un temps fou à buter sur des détails : sleep() est apparement réservé pour les machine windows et ne fonctionne pas sur tous les linux : sur ma machine Fedora, c'est accepté à la compilation et fonctionne comme attendu, sur la RasPi, ça passe pas, il faut remplacer par usleep() !
    T'imagines un peu le cirque que ça te met dans le cerveau d'un débutant, et le temps que ça peu prendre pour trouver l'astuce !

  18. #18
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 369
    Points : 23 623
    Points
    23 623
    Par défaut
    Bonsoir,

    Citation Envoyé par hary66 Voir le message
    Je sens un peu d'agacement dans la réponse. Désolé, mais je n'ai aucune réponse à ces questions.
    Non, je te rassure, aucun agacement dans ces remarques. J'essayais au contraire de titiller ta curiosité.

    Je suis en déplacement jusqu'à ce soir. Essaie de nous dire ce que tu en penses et on essaiera de faire le point dans la soirée...

  19. #19
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 369
    Points : 23 623
    Points
    23 623
    Par défaut
    Re-bonjour,

    Citation Envoyé par hary66 Voir le message
    Oui, j'ai bien vu, mais les seuls petits exercices faciles à mettre en oeuvre que j'ai pu trouver concernaient signal(). […] Désolé, mais je n'ai aucune réponse à ces questions.
    En fait, toutes TES questions sont parfaitement légitimes et détecter l'appui sur une touche du clavier devrait effectivement faire partie des fondamentaux de la programmation.

    Pourquoi, dans ce cas, est-ce si confus ? Parce qu'il faut distinguer « ce qu'il est possible de faire en langage C », c'est-à-dire virtuellement tout, et « ce qui est défini par la norme C » en particulier. Et la norme refuse de statuer sur le cas du clavier puisque non seulement celui-ci peut-être très différent d'une machine à l'autre, mais certaines peuvent ne pas en être équipées du tout…*à commencer par ton Raspberry Pi. Avec cela, il faut se souvenir qu'à l'époque où le langage C a été mis au point, les programmes fonctionnaient sur des mainframes auxquelles les utilisateurs accédaient via des terminaux. Donc, en fait, dès le départ, les processus communiquaient avec l'extérieur au travers de connexions série. Dans ce modèle, le caractère n'est envoyé qu'une fois que l'utilisateur l'a dûment saisi et il n'est pas possible de savoir plus précisément (ni instantanément) quel est l'état exact du clavier.

    Le langage C, en revanche, considère qu'un processus doit communiquer avec l'extérieur en recevant des informations et en les renvoyant. Pour cela, il définit (si c'est possible, car ce n'est pas obligatoire non plus), une « entrée standard » et une « sortie standard », plus une « sortie d'erreur standard », qui sont en fait des flux qui sont gérés comme des fichiers. Et ce sont ces entrées et sorties standard qui sont, elles, reliés aux sources et destination de données en vigueur à l'exécution du programme.

    Par défaut, donc, l'entrée standard de ton processus est associée à la liaison série sur laquelle est branché ton terminal, ou ce qui en tient lieu, c'est à dire un terminal virtuel sous X-Window et un périphérique /dev/pty ou /dev/pts pour émuler le fonctionnement de la liaison série, MAIS ton processus ne le sait pas ! Il ne peut pas savoir a priori si les caractères qu'il lit proviennent d'un fichier (ce qui se passe effectivement lorsque tu utilises « < » sur la ligne de commande), du clavier, d'une liaison série ou réseau externe, ou de la sortie d'un autre processus.

    C'est effectivement important parce que tu peux te retrouver dans d'autres environnements de développement où toutes ces questions ont été tranchées « par convention » et qui proposent alors des primitives pour gérer tout cela sans se soucier de leur implémentation. C'était notamment le cas des BASIC des huit bits entre 1980 et 1990 sur lesquels beaucoup de gens de ma génération ont fait leurs armes, et qui équipait en ROM la plupart de ces machines et qui leur servait de système d'exploitation. On avait des choses comme INPUT, INKEY$ ou INPUT$ qui permettaient de faire cela mais même alors, gérer proprement un événement clavier au milieu d'une tâche en continu s'avérait difficile (il se pouvait que l'appui ne soit pas pris en compte s'il n'avait pas exactement lieu au moment où on traitait l'instruction).

    Donc, vu comme cela, on dit au système « lis-moi un caractère en entrée » et c'est de la responsabilité de ce système de garantir la disponibilité de ce caractère. S'il lit un fichier, il va donc attendre que le disque démarre, que la lecture se fasse proprement, qu'il recommence plusieurs fois en cas d'erreurs, qu'il l'extraie au milieu du secteur qu'il a rapatrié et, qu'enfin, il le dépose dans le buffer d'entrée du processus. Et ce n'est qu'à l'issue de cette procédure que l'appel système va rendre la main à ton programme. C'est en ce sens que l'appel est dit « bloquant ». Quand il lit un disque ou une bande magnétique, les choses sont à peu près claires mais quand il lit une connexion réseau, série, un pipe relié à la sortie d'un autre programme ou tout simplement le clavier, rien ne permet d'estimer à l'avance le temps que cela va prendre. Tout dépend du bon vouloir de l'émetteur et, en ce sens, il n'y a rien d'autre à faire qu'attendre que la donnée arrive.

    Il est important de noter également que le fait d'attendre n'est pas non plus une condition imposée par le langage C (et donc, ce n'est pas non plus dans la norme). C'est un effet de bord : une nécessité technique pour honorer la commande.

    Tout ceci nous amène à une première conclusion : gérer ce problème va relever de la programmation système et, à ce titre, il va falloir choisir sur quoi agir. Ceci nous oblige donc à nous spécialiser un peu et toute l'astuce va consister à rester le plus portable possible, en choisissant notamment une norme qui soit la plus répandue possible. Et plus généralement :

    • Quel est mon problème ? → Détecter l'appui sur une touche pendant un traitement en cours ;
    • En quoi est-ce un problème ? → Parce que je ne peux pas faire deux choses à la fois : surveiller le clavier et traiter mes données ;
    • Peut-on résoudre ce problème d'un point de vue uniquement algorithmique, c'est-à-dire indépendamment du langage et de la plateforme utilisée ? → Oui, dans un premier temps : je peux faire une boucle principale dans laquelle je vais lire la touche et faire une partie raisonnable de mon traitement à chaque passe, par exemple lire une ligne d'un fichier de données ;
    • Est-ce que ça suffit à résoudre mon problème ? → Non ;
    • Pourquoi ? → Parce que lire le clavier m'oblige à rester coincé sur l'appel et ce, pour les raisons exposées ci-dessus ;
    • De quoi aurais-je besoin pour que cela fonctionne ?A minima, de pouvoir savoir à l'avance si des données sont disponibles, soit de faire en sorte que l'appel ne soit plus bloquant mais échoue s'il n'y a rien à lire, soit encore d'être prévenu par l'extérieur si quelque chose devient disponible ;
    • Est-ce possible en C standard, c'est-à-dire en n'utilisant que ce qui est proposé par la norme ? Malheureusement non, parce que ces considérations d'appels bloquants sont dus aux dispositifs scrutés eux-mêmes, et sont en dehors de la norme ;
    • À quoi dois-je m'étendre dans ce cas ? C'est là qu'il faut faire le bon choix : tu vas avoir besoin d'une dépendance à quelque chose, mais il faut choisir la bonne : tant qu'à faire, quelque chose qui soit le plus portable et le plus répandu possible, qui soit simple à utiliser, qui ne demande pas de faire de grosses initialisations préalables, qui ne demande pas non plus de s'y former avant de l'utiliser et donc l'empreinte système (mémoire, espace disque ou utilisation CPU) soit la plus minime possible. La bonne idée est de se tourner en premier lieu vers POSIX, qui a justement été écrite parce que tout le monde a eu le même problème.


    POSIX signifie « Portable Operating System Interface » et reprend en fait la plupart des appels système propres à UNIX mais qui sont généralement tellement populaires (et usités) qu'on croit souvent qu'ils sont définis par la norme C elle-même, et ce en bonne partie pour les raisons historiques dont on a parlé. C'est le cas entre autres de open() et close(), qui servent à gérer les fichiers et qui sont les appels système sur lesquels s'appuient fopen() et fclose() définies, eux, par la norme C. Le fait d'avoir fait une norme et permet en principe à tout programme UNIX bien écrit de fonctionner sur tous les systèmes annonçant respecter cette norme (en tout cas, la prendre en charge), à commencer par Windows.

    Et alors, à ce sujet, il est possible de passer des flags à open() pour lui indiquer comment on souhaite qu'il gère un fichier, notamment O_NONBLOCK pour lui dire de fonctionner en mode non-bloquant, c'est-à-dire faire échouer un appel qui ne peut être honoré plutôt qu'attendre qu'il le soit.

    Il est également possible de modifier les flags d'un fichier déjà ouvert à l'aide de la fonction de contrôle de fichiers nommée fcntl() : c'est ce que l'on fait, et c'est nécessaire ici car ce n'est pas nous qui ouvrons manuellement l'entrée standard : on en hérite à la naissance du processus.


    Tout ceci nous donne la réponse à la première et à la deuxième question : le problème a été « résolu » a priori par le simple ajout de la ligne 9 dans mon programme précédent : l'appel à fcntl() pour passer O_NONBLOCK. Maintenant, à chaque fois, l'appel échoue avec EOF s'il n'y a rien à lire.

    À la troisième question : « pourquoi est-on obligé de valider avec Entrée » ? Là, malheureusement, pour une raison indépendante de ton programme et du noyau du système d'exploitation : l'utilisateur utilise un terminal, et ce terminal est généralement inféodé à une « discipline de ligne », c'est-à-dire que le dispositif d'exploitation du terminal est fait pour qu'il bufferise lui-même sa propre saisie, et qu'il ne l'envoie au destinataire qu'une fois validé avec Entrée. C'était la même chose avec Télétel et la touche Envoi du Minitel, à titre de comparaison.

    C'est utile si par exemple, tu écris :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include <stdio.h>
     
    int main (void)
    {
        char buffer[256];
     
        fgets (buffer, sizeof buffer, stdin);
        printf ("Bonjour %s\n",buffer);
     
        return 0;
    }
    Ce programme demande son nom à l'utilisateur, puis lui adresse un « bonjour ». L'utilisateur saisit son nom, puis peut éventuellement le corriger avec Backspace et le resaisir au propre avant de l'envoyer alors que rien, dans ton programme, n'est prévu pour gérer ce cas de figure. Donc, dans ce type d'environnement (c'est-à-dire la console habituelle sous Unix), il te faudra aussi à terme explorer termios pour dialoguer avec ce terminal et lui demander de t'envoyer directement les caractères saisis. C'est un problème habituel également. La bonne nouvelle, c'est que quand ces deux choses sont réglées, alors ça marche ! :-)

    À la dernière question, enfin, pourquoi ne faut-il pas suivre cet exemple à moyen terme ? Parce que ça reste sale, parce que cela t'oblige à faire du polling (traduire par « scrutation ») : tu es obligé de faire une boucle infinie qui scanne le clavier en permanence, donc qui occupe potentiellement la ressource et, surtout qui occupe 100 % du CPU.

    Que peut-on explorer d'autre ? select() (de préférence), ncurses, SIGIO, les MVC des interfaces graphiques, et seulement enfin les threads proposés par ternel un peu plus haut. Mais ça, ça fera l'objet d'un prochain commentaire. ;-)

  20. #20
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    À noter que select() sur l'entrée standard est spécifique au monde POSIX; ça ne marche pas sur d'autres systèmes comme Windows, où select() ne marche que sur des sockets (connexions réseau). En revanche, il existe des versions multi-plateformes de ncurses.
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

Discussions similaires

  1. Déclaration de variable pour un template -> comment faire ?
    Par souffle56 dans le forum XSL/XSLT/XPATH
    Réponses: 14
    Dernier message: 16/03/2010, 23h27

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