OVH Cloud OVH Cloud

comprendre getchar()

59 réponses
Avatar
bpascal123
Je trouve getchar() assez difficile =E0 comprendre. Au lieu de saisir du
clavier, getchar lit directement dans la m=E9moire? Presser une touche
du claver entraine un signal dans le bios qui "renvoie" ce signal dans
le kernel puis dans la m=E9moire (buffer) et de ce dernier est affich=E9
le caract=E8re de la touche press=E9e (par ex.: a) pr=E9c=E9demment. Par
contre le caract=E8re reste toujours dans le buffer mais dans la partie
"ancienne" du buffer. Dans la partie "neuve", se trouverait les
caract=E8res qui n'ont pas =E9t=E9 affich=E9s =E0 l'=E9cran ou trait=E9s d'=
une autre
mani=E8re.

Ce code d'apparence simple pour la saisie me laisse perplexe:

#include <stdio.h>

int main(void)
{
char c ;
c =3D getchar() ;

while ( c !=3D EOF )
{
putchar(c) ;
c =3D getchar() ;
}
return 0 ;
}

Je me demande si un petit apprentissage de l'assembleur jusqu'=E0
comprendre ce code, ne serait pas utile dans ce cas?

10 réponses

2 3 4 5 6
Avatar
Jean-Marc Bourguet
Antoine Leca writes:

Jean-Marc Bourguet a écrit :
(Avec fread il faut comparer la valeur retournee avec la
taille du buffer, si elles sont differentes, il y a fin de fichier ou
erreur de lecture; c'est avec read(2) sous Unix qu'on peut avoir des
lectures partielles)





Je ne suis pas certain. À la différence de fwrite() qui spécifie que
The fwrite function returns [...] which will be
less than nmemb only if a write error is encountered.
^^^^
le texte pour fread n'a pas ce "only". Et je serais TRÈS surpris que ce
soit un oubli :-) en particulier avec la nécessité de supporter les
fichiers « à la MSDOS » où il faut après lecture convertir les CR+LF en
n, et aussi les fichiers « à la mainframe » où ce sont les de
bourrage en fin de record qui sont éliminés, même sur un fichier "rb"...



Le texte de fread est

The fread function returns the number of elements successfully read,
which may be less than nmemb if a read error or end-of-file is
encountered.

Je comprends ton interprétation et elle me semble la bonne. En passant, un
autre cas plus courant, celui des flux interactifs, montre qu'elle est
préférable.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Samuel DEVULDER
Antoine Leca a écrit :
Samuel DEVULDER écrivit :
Je ne comprends pas bien.



Si je veux faire rire, j'isole cette phrase...


Je ne comprends pas bien. Tu aurait voulu quoi au juste?



Ce que JE n'arrive pas à comprendre, c'est le propos de contester
l'utilisation de EOF, à remplacer par feof(),



Non je conteste pas l'utilisation de EOF.. je signale juste que feof()
existe et peut être utilisé pour detecter la fin de fichier. C'est tout.
Comme indiqué EOF peut signifier fin de fichier *ou* erreur de lecture.
C'est pas toujours équivalent (l'un est normal, l'autre une erreur).

au motif que pour d'autres
fonctions la valeur retournée par la fonction d'ES dans le cas similaire
est NULL ou 0, alors même que l'utilisation de cette fonction peut
parfois amener des résultats incorrects.

Par exemple, si on reprend ta formulation légèrement modifiée :
s = fgets(s, sizeof s, FLUX);



Et le test à NULL? Ca change tout.

if(fgets(s, sizeof s, FLUX)) {traitement}

L'absence de NULL en retour indique qu'on a des données valides et que
l'on peut effectuer un traitement. Ce même schéma/idiome s'applique
aussi à fread() et autre fmachin() il me semble (sauf que le test varie
suivant les fonctions bien entendu).

if(feof(FLUX)) break;



Ensuite si on a atteint la fin de fichier, on sort de la boucle. Normal.

Si la fin de fichier n'est pas « protégée » par un n, elle va être
mangée par le break... Un joli bogue silencieux, facile à oublier en
test, en attente d'un jeu de données imprévu pour le faire monter...



Ce code n'est pas vraiment la formulation d'origine. fgets() et fgetc()
ne sont pas vraiment la même chose du point de vu de la fin de fichier.
Tu peux faire un feof() juste après un fgetc() et ignorer la valeur
retournée par la définition même de l'EOF. En revanche pour le fgets(),
tu dois tester la valeur de retour pour savoir si tu as des data
exploitables, et tester le feof() pour décider s'il faut ou non reboucler.

Qu'en fin de fichier scanf() retourne un buffer non rempli? C'est
justement le cas si tu lis passé le feof()==true.



Dans l'essai de Jean-Marc, scanf() a lu "tutu", et en même temps touché
la fin du fichier, donc l'indicateur est passé à 1 ; puis a rempli le
tampon avec tutu comme demandé.



C'est comme cela que je l'ai compris en effet.

Ce n'est qu'au coup suivant où scanf()
va réessayer de lire, étant positionné à la fin du fichier cela va
échouer et donc retournera enfin EOF : mais à l'itération précédente il
pourra/devra traiter le tutu.


J'ai un peu le sentiment que l'erreur, ou le hiatus, est de considérer
que scanf() retourne un truc en relation directe avec la fin de fichier.



Voilà. C'est comme cela que fonctionne le C : la fin de fichier est une
condition « anormale », à traiter en sortie de boucle.



On est presque d'accord.. sauf que le feof() n'est pas vraiment un cas
anormal. Je dirais même l'inverse: c'est un cas systématique. Le cas
anormal c'est par exemple fread() ou fscanf() qui ne retournent pas le
nombre attendu, etc. Dans mon cas les cas anormaux conduisent plutôt à
des msgs d'erreur + sortie de boucle (éventuellement avec un beau goto
ou longjmp), mais le feof() est plutôt lui que j'utilise pour sortir de
boucle (cf exemple du do..while()).

Jean-Marc Bourguet a écrit :
(Avec fread il faut comparer la valeur retournee avec la
taille du buffer, si elles sont differentes, il y a fin de fichier ou
erreur de lecture; c'est avec read(2) sous Unix qu'on peut avoir des
lectures partielles)





Je ne suis pas certain. À la différence de fwrite() qui spécifie que
The fwrite function returns [...] which will be
less than nmemb only if a write error is encountered.
^^^^
le texte pour fread n'a pas ce "only". Et je serais TRÈS surpris que ce
soit un oubli :-)



En même temps, fread() lit un truc bufferisé et rien de l'empêche de
poursuivre sa lecture tant qu'il n'a pas rencontré un échec, non?

en particulier avec la nécessité de supporter les
fichiers « à la MSDOS » où il faut après lecture convertir les CR+LF en
n, et aussi les fichiers « à la mainframe » où ce sont les de
bourrage en fin de record qui sont éliminés, même sur un fichier "rb"...



Il faudrait voir. Tout est dans le "only". C'est subtil, mais important
(ca peut causer des bugs un truc pareil).

Ben is donc, la question anodine de bpascal123 nous emmène loin, très
loin... :)

a+

sam.
Avatar
Jean-Marc Bourguet
Jean-Marc Bourguet writes:

Antoine Leca writes:

Jean-Marc Bourguet a écrit :
(Avec fread il faut comparer la valeur retournee avec la
taille du buffer, si elles sont differentes, il y a fin de fichier ou
erreur de lecture; c'est avec read(2) sous Unix qu'on peut avoir des
lectures partielles)





Je ne suis pas certain. À la différence de fwrite() qui spécifie que
The fwrite function returns [...] which will be
less than nmemb only if a write error is encountered.
^^^^
le texte pour fread n'a pas ce "only". Et je serais TRÈS surpris que ce
soit un oubli :-) en particulier avec la nécessité de supporter les
fichiers « à la MSDOS » où il faut après lecture convertir les CR+LF en
n, et aussi les fichiers « à la mainframe » où ce sont les de
bourrage en fin de record qui sont éliminés, même sur un fichier "rb"...



Le texte de fread est

The fread function returns the number of elements successfully read,
which may be less than nmemb if a read error or end-of-file is
encountered.

Je comprends ton interprétation et elle me semble la bonne. En passant, un
autre cas plus courant, celui des flux interactifs, montre qu'elle est
préférable.



Mais j'ai essayé dans ce cas et Linux, netBSD, freeBSD et Solaris se
comportent avec ma première interprétation.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
bpascal123
Est-ce qu'il y a un lien entre l'affichage d'un caractère à l'écran e t
cette commande assembleur : int 0x80 ou quelque chose comme ça... Ca
vient d'un document d'introduction à l'assembleur pour Linux que je ne
peux lire que pendant la pause à midi ou tard en fin de journée.... Je
sais que int c'est pour interruption... et que ça concernerait des
registres comme eax ou ebx ... -merci
Avatar
Samuel DEVULDER
a écrit :
Est-ce qu'il y a un lien entre l'affichage d'un caractère à l'écran et
cette commande assembleur : int 0x80 ou quelque chose comme ça... Ca
vient d'un document d'introduction à l'assembleur pour Linux que je ne
peux lire que pendant la pause à midi ou tard en fin de journée.... Je
sais que int c'est pour interruption... et que ça concernerait des
registres comme eax ou ebx ... -merci



Oulala. Il doit y avoir un lien, mais très éloigné. L'appel aux
primitives de l'OS se fait typiquement à coup d'interruptions
logicielles. Donc usage de l'instruction "int" sur x86. Après pour les
paramètres de cet instruction, il faut regarder du coté de l'OS. Sous
linux, la routine system_call se fait avec l'int 0x80.

http://www.win.tue.nl/~aeb/linux/lk/lk-4.html

Mais franchement, tout ceci n'est d'aucune utilité pour apprendre le C.
Parce que si tu regardes dans les détails, tu te rendra compte que sous
l'instruction tu trouvera d'autres appels, d'autres mécanismes, et tu
demandera comment "il fait le CPU" pour ceci ou cela. Le puits est
quasiment sans fond. A un moment il faut arrêter de regarder sous le
capot et abstraire ce qu'il se passe. Typiquement fais toi une image
mentale du fonctionnement de l'API C standard et ne regarde pas les
interruptions. Plus personne n'utilise cela de nos jours en programmation.

sam (enfin presque plus personne, mais en tout cas aucun débutant)
Avatar
Samuel DEVULDER
a écrit :
Est-ce qu'il y a un lien entre l'affichage d'un caractère à l'écran et
cette commande assembleur : int 0x80 ou quelque chose comme ça... Ca
vient d'un document d'introduction à l'assembleur pour Linux que je ne
peux lire que pendant la pause à midi ou tard en fin de journée.... Je
sais que int c'est pour interruption... et que ça concernerait des
registres comme eax ou ebx ... -merci



Tu es paradoxal. D'un coté tu nous dis ne pas vouloir passer du temps
pour comprendre les opération de bases sur les bits des entiers (or,
xor, and, not etc), et de l'autre tu te jettes les yeux fermés dans les
interruptions logicielles où les opérations sur les bits ne sont même
pas le niveau élémentaire requis pour comprendre.

Si tu veux faire de l'ASM, tu va déguster des tas de trucs bien plus
complexes que de comprendre pourquoi si on a int x, y, alors x & ~x ==
0, -x == 1 + ~x et ~(x & y) = (~x) | (~y), j'en passe et des meilleures.

sam.
Avatar
tth
a raconté :

Est-ce qu'il y a un lien entre l'affichage d'un caractère à l'écran et
cette commande assembleur : int 0x80 ou quelque chose comme ça... Ca



int 0x80 permet de déclencher un appel à une fonction de la
kernelle Linux, donc oui, l'appel système write passera par
l'instruction int 0x80. Mais tu ne devrais vraiment ne pas
te préoccuper de ça tant que tu n'as pas compris les choses
qui sont au dessus.

--
Seeking for a new job : http://tboudet.free.fr/cv.html
Avatar
Jean-Marc Bourguet
"" writes:

Est-ce qu'il y a un lien entre l'affichage d'un caractère à l'écran et
cette commande assembleur : int 0x80 ou quelque chose comme ça... Ca
vient d'un document d'introduction à l'assembleur pour Linux que je ne
peux lire que pendant la pause à midi ou tard en fin de journée.... Je
sais que int c'est pour interruption... et que ça concernerait des
registres comme eax ou ebx ... -merci



Un OS offre un certain nombre de services. La maniere d'appeler ces
services dependent de l'OS et de l'architecture pour les OS qui sont
disponibles sur plusieurs architectures.

La maniere moderne de faire, c'est de fournir une ou des libs dynamiques
qui sont l'interface officielle. Ca permet de changer la repartition des
taches entre les differents composants sans avoir de problemes de
compatibilite. C'est a ma connaissance la methode conseillee sous Linux.
En methode alternative, les OS permettent parfois de lier statiquement avec
ces libs.


Linux sur x86 utilisait effectivement l'interuption 0x80 pour communiquer
entre la partie utilisation et la partie noyau (ca m'etonnerait un peu que
ce soit toujours la methode preferee, il y a depuis un certain temps des
methodes plus performantes pour faire la meme chose). Les parametres sont
passes dans des registres. Rares sont les gens qui ont a s'occuper de ce
genre de details. Les connaitre aide certaines personnes a comprendre les
choses, et bloque d'autres qui n'arrivent pas a passer outre les details
d'une implementation particuliere.

La programmation consiste a utiliser et a construire des abstractions.
Meme le langage machine n'est jamais qu'une interface avec une
implementation derriere qui change. Beaucoup.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Antoine Leca
Samuel DEVULDER écrivit :
Antoine Leca a écrit :
Par exemple, si on reprend ta formulation légèrement modifiée :
s = fgets(s, sizeof s, FLUX);



Et le test à NULL? Ca change tout.

if(fgets(s, sizeof s, FLUX)) {traitement}

if(feof(FLUX)) break;





Effectivement, cela change tout. Cela passe de « je ne suis pas d'accord
avec ce que tu écris » à « je préfère if(OK)BOSSE;if(!OK)DEHORS; à
if(!OK)break;BOSSE; » qui pour moi relève du style et que je ne
commenterai pas.


En même temps, fread() lit un truc bufferisé



Pas obligatoirement. fread() marche AUSSI après setvbuf(,_IONBF,)


Antoine
Avatar
Antoine Leca
tth a écrit :
a raconté :

Est-ce qu'il y a un lien entre l'affichage d'un caractère à l'écran et
cette commande assembleur : int 0x80 ou quelque chose comme ça... Ca





OEQLC?


int 0x80 permet de déclencher un appel à une fonction de la
kernelle Linux,



Oui (pour i386).

donc oui, l'appel système write passera par l'instruction int 0x80.



Non ! Plus depuis une petite dizaine d'années. Aujourd'hui c'est
sysenter/syscall. Et c'est dans un groupe Linux qu'on aura plus de
détail, cela n'a rien à voir avec le C.


@bpascal: Attention quand même : contrairement au C, l'assembleur n'est
pas vraiment normalisé, pas du tout portable, et qui plus est évolue
suffisamment vite pour rendre rapidement obsolète un cours
d'introduction. En gros, si tu n'en as pas réellement un besoin précis,
en 2010 c'est inutile de passer du temps à apprendre la programmation en
assembleur. Ah! Et de plus les drivers sont écrits en C.


Antoine
2 3 4 5 6