Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

transformer un tampon en lignes

56 réponses
Avatar
Benoit Izac
Bonjour,

Je cherche à faire une fonction qui me permette de transformer un tampon
en lignes de façon à traiter ces dernières par la suite.

J'ai un bout de code qui fonctionne :
========================================================================
#include <stdio.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define BS 10
#define LM 100

int get_line(const char *buffer, size_t *bp, size_t bs,
char *line, size_t *lp, size_t ls)
{
while (*bp < bs) {
/* not enough space in line */
if (!(*lp < ls)) {
(*bp)--;
line[(*lp)-1] = 0;
(*lp) = 0;
return -1;
}
/* end of file? */
if (buffer[*bp] == EOF) {
printf("end of file?\n");
line[*lp] = 0;
return 0;
}
/* end of line */
if (buffer[*bp] == '\n') {
line[*lp] = 0;
(*lp) = 0;
(*bp)++;
return 1;
}
line[*lp] = buffer[*bp];
(*lp)++;
(*bp)++;
}
/* end of buffer */
(*bp) = 0;
line[*lp] = 0;
return 0;
}


int main(int argc, char *argv[])
{
int fd;
size_t r;
char buf[BS];
char line[LM];
size_t bp = 0;
size_t lp = 0;

if (argc != 2)
exit(1);

fd = open(argv[1], O_RDONLY);
if (fd < 0)
exit(2);

while((r = read(fd, buf, BS))) {
while (get_line(buf, &bp, r, line, &lp, LM))
printf("%s\n", line);
}

close(fd);
return 0;
}
========================================================================
PS : Désolé pour ceux qui ne sont pas sous Unix, j'ai bien essayé de
faire la même chose avec fread() mais cette dernière ne me renvoie pas
la taille lu, ce qui pose un problème à la dernière lecture où r est la
plupart du temps différent de BS.

Mon choix lorsque la taille de line est trop petite est discutable
(vaut-il mieux tronquer les lignes ?) mais ce n'est pas vraiment ce qui
me pose problème.

Les questions :
* Ce code est-il correct (fonctionne-t-il dans tous les cas) ?
* Est-il possible d'alléger le code (que get_line est moins
d'arguments) en déclarant bp et lp static ?
* Existe-t-il une autre solution (fonction toute faite, autre
algorithme) ?

Merci.
--
Benoit Izac

10 réponses

1 2 3 4 5
Avatar
Benoit Izac
Dans le message , le 30/07/2010 à 09:39, j'ai
écrit :

* Est-il possible d'alléger le code (que get_line est moins
d'arguments) en déclarant bp et lp static ?



======================================================================= #include <stdio.h>
#include <stdlib.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define BS 10
#define LM 100

char *get_line(const char *buffer, size_t bs)
{
static char l[LM] = { 0 };
static size_t bp = 0; /* buffer position */
static size_t lp = 0; /* line position */

while (bp < bs) {
/* line too short */
if (lp == LM) {
fprintf(stderr, "line too shortn");
exit(1);
}

/* end of line */
if (buffer[bp] == 'n') {
l[lp] = 0;
lp = 0;
bp++;
return l;
}

l[lp] = buffer[bp];
lp++;
bp++;
}

/* end of buffer */
bp = 0;
return NULL;
}


int main(int argc, char *argv[])
{
int fd;
size_t r;
char buf[BS];
char *line = NULL;

if (argc != 2)
exit(1);

fd = open(argv[1], O_RDONLY);
if (fd < 0)
exit(2);

while((r = read(fd, buf, BS))) {
while ((line = get_line(buf, r)))
printf("%sn", line);
}

close(fd);
return 0;
}
=======================================================================
C'est nettement plus lisible comme ça. Par contre j'ai un gros doute sur
le « static char l[LM] » : est-ce que sa durée de vie est garantie
pendant toute la durée du programme ?

--
Benoit Izac
Avatar
Xavier Roche
Benoit Izac a écrit :
J'ai un bout de code qui fonctionne :

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>



Inutiles à priori ici.

Le code est un poil compliqué, et de plus il est faux: read() va couper
les lignes en plein milieu, chose qui n'est pas gérée ici.

Le "buffer[*bp] == EOF" est faux aussi: EOF n'est pas un caractère, il
est retourné de manière occasionnelle pour signaler "fin de fichier" en
tant que valeur de retour, mais en aucun cas comme caractère: EOF n'est
PAS un "caractère", il n'a aucune existence physique [je laisse tomber
les subtilités du mode texte ancestral qui acceptait de tels délimiteurs
dans des cas précis - tout cela date de la préhistoire et peut être oublié]

Typiquement fgetc() retourne un nombre entre 0 et 255 (inclus) quand
tout se passe bien, et -1 en cas d'erreur. Evidemment, caster le retour
en (char) est une erreur, car on ne peut alors plus faire la différence
entre le caractère 0xff et EOF.

while((r = read(fd, buf, BS))) {



Attention aussi, read() retourne un résultat non nul en cas d'erreur.
(boucle infinie en cas d'erreur, donc). Il retourne 0 en cas de .. end
of file.

while((r = read(fd, buf, BS)) > 0) {

PS : Désolé pour ceux qui ne sont pas sous Unix, j'ai bien essayé de
faire la même chose avec fread() mais cette dernière ne me renvoie pas
la taille lu, ce qui pose un problème à la dernière lecture où r est la
plupart du temps différent de BS.



Euh, fread(buffer_dest, 1, nombre_octets, file) renvoi le nombre
d'octets (le "1" est la taille des record à lire, càd ici 1 octet).

* Existe-t-il une autre solution (fonction toute faite, autre
algorithme) ?



char *fgets(char *s, int size, FILE *stream);
me semble une bonne piste. (stdio.h)
Avatar
Benoit Izac
Bonjour,

le 30/07/2010 à 19:02, Xavier Roche a écrit dans le message
<i2v0js$nu7$ :

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>



Inutiles à priori ici.



read() est dans unistd.h, open() demande les deux autres.

Le code est un poil compliqué, et de plus il est faux: read() va
couper les lignes en plein milieu, chose qui n'est pas gérée ici.



Il me semble justement que si (en tout cas ça fonctionne quelque soit le
taille BS).

Le "buffer[*bp] == EOF" est faux aussi: EOF n'est pas un caractère, il
est retourné de manière occasionnelle pour signaler "fin de fichier"
en tant que valeur de retour, mais en aucun cas comme caractère: EOF
n'est PAS un "caractère", il n'a aucune existence physique [je laisse
tomber les subtilités du mode texte ancestral qui acceptait de tels
délimiteurs dans des cas précis - tout cela date de la préhistoire et
peut être oublié]

Typiquement fgetc() retourne un nombre entre 0 et 255 (inclus) quand
tout se passe bien, et -1 en cas d'erreur. Evidemment, caster le
retour en (char) est une erreur, car on ne peut alors plus faire la
différence entre le caractère 0xff et EOF.



C'est noté.

while((r = read(fd, buf, BS))) {



Attention aussi, read() retourne un résultat non nul en cas d'erreur.
(boucle infinie en cas d'erreur, donc). Il retourne 0 en cas de .. end
of file.

while((r = read(fd, buf, BS)) > 0) {



Tout à fait (c'était juste un rapide exemple pour remplir buf, je
n'utilise pas read() dans mon programme).

PS : Désolé pour ceux qui ne sont pas sous Unix, j'ai bien essayé de
faire la même chose avec fread() mais cette dernière ne me renvoie pas
la taille lu, ce qui pose un problème à la dernière lecture où r est la
plupart du temps différent de BS.



Euh, fread(buffer_dest, 1, nombre_octets, file) renvoi le nombre
d'octets (le "1" est la taille des record à lire, càd ici 1 octet).



Effectivement, je l'utilisais dans l'autre sens :
fread(buffer_dest, nombre_octets, 1, file) et là, à la dernière lecture,
il me renvoyait 0 si elle n'était pas complète.

D'ailleurs, ça change quelque chose au niveau des performances de dire
je veux faire une lecture de N octets ou je veux N lectures de 1 octet ?

* Existe-t-il une autre solution (fonction toute faite, autre
algorithme) ?



char *fgets(char *s, int size, FILE *stream);
me semble une bonne piste. (stdio.h)



C'est justement là qu'est mon problème, je ne peux pas l'utiliser car je
n'ai pas un FILE* mais un tampon qui est mis à jour par une fonction
(exactement comme le read() ici).

En fait, avec le read(), j'ai trouvé une astuce avec fdopen() mais elle
n'est pas utilisable avec la fonction que j'utilise (archive_read_data()
de la libarchive si ça en intéresse).

--
Benoit Izac
Avatar
espie
In article ,
Benoit Izac wrote:
======================================================================= >PS : Désolé pour ceux qui ne sont pas sous Unix, j'ai bien essayé de
faire la même chose avec fread() mais cette dernière ne me renvoie pas
la taille lu, ce qui pose un problème à la dernière lecture où r est la
plupart du temps différent de BS.



Unix ou pas, read() est une mauvaise idee dans ce contexte, lire les
donnees 10 octets par 10 octets, c'est tres inefficace.

Si tu es sous Unix, il y a un getline() dans posix 2008.

Sinon, ben en standard, on ecrit ta fonction autour de fgets(), mais
c'est un peu casse-gueule a ecrire au debut.

Vu que tu n'es pas arrive a voir comment marche fread() (en particulier,
trouver la taille lue), c'est inquietant pour fgets().

Cote choix des identifiants, je remplacerais BS par autre chose de plus
explicite (oui, BUFSIZE c'est plus clair), parce que la, mon cerveau
oscille entre "Bullshit" et "Bjarne Stroustrup" sans passer par la bonne
case...
Avatar
Samuel DEVULDER
Benoit Izac a écrit :

* Est-il possible d'alléger le code (que get_line est moins
d'arguments) en déclarant bp et lp static ?



Bof. Les choses "static" cassent la re-entrance (appels concurrentiels
dans le même espace mémoire: thread, interruptions, etc).

Une solution possible pour réduire les params est d'utiliser un pointeur
sur struct en argument. Si de plus ta fonction a des variables d'états
tu peux les déclarer dans la struct et avoir un code ré-entrant du coup.

Tu peux même regrouper les champs de la structure pour avoir tous les
paramètres d'entrée dans une sous-structure "in" et ceux de sortie dans
"out" (et les états dans un "state").

DIGRESSION:

Contrairement aux fonctions où les paramètres sont anonymes à
l'extérieur, avec une structure tu peux accèder aux paramètres de ta
fonction via leur nom en plus de leur position. Ca rend les choses plus
claires. Compare par exemple:

plot(3,128,12);
// heuuu 128 c'est quoi déjà? la couleur ou l'abscisse ?
// ..
// Attends ctrl-f3 que je retrouve la definition de la
// fonction dans le fichier H
// ..

extern void plot(int color, int y, int x);
// Ah ouais ok c'est donc un "y8" en fait.

avec:

struct plot arg;

arg.color = 3; arg.x = 12; arg.y = 128;
plot(&arg);

Tu peux même pousser le luxe jusqu'à écrire:

#define plot2(arg) do {struct plot tmp = arg; plot(&tmp);} while(0)

Et utiliser une écriture où les paramètres sont carrément nommés et où
l'ordre n'intervient plus:

plot2({.x = 12, .y = 128, .color = 3});

On peut même aller plus loin et prendre la convention que la struct et
la fonction ont le même nom et utiliser la macro:

#define call(fcn, arg) {struct fcn tmp = arg; fcn(&tmp);} while(0)

Après on aime ou pas, on utilise ou pas c'est comme on veut. Mais c'est
une possibilité permise.

sam.
Avatar
Antoine Leca
Benoit Izac a écrit :
Bonjour,

le 30/07/2010 à 19:02, Xavier Roche a écrit dans le message
<i2v0js$nu7$ :

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


Inutiles à priori ici.



read() est dans unistd.h, open() demande les deux autres.



Je suppose que Xavier voulait dire qu'il était inutile d'obliger à
utiliser des entêtes POSIX, lorsque on peut se satisfaire d'un
environnement normalisé C, plus répandu. Mais bon, je vois que tu as
expliqué le pourquoi du comment par la suite (libarchive).

En ce qui concerne <sys/stat.h>, je ne vois pas l'intérêt, et encore
moins la liaison avec open().


D'ailleurs, ça change quelque chose au niveau des performances de dire
je veux faire une lecture de N octets ou je veux N lectures de 1 octet ?



Au niveau des performances, non sauf cas extrêmement particuliers (et
qui ne concerne pas des machines Unix ou même Windows) ; au niveau du
contrôle d'erreur oui, tu as touché du doigt la différence !

Et en ce qui concerne l'intérêt d'avoir deux constantes, c'est surtout
destiné aux systèmes qui opèrent en direct sur des bouzins organisés par
bloc : par exemple, on pourrait imaginer un accès à un disque dur, et là
ce serait intelligent d'utiliser 1 ou plusieurs blocs de 512 caractères.


Antoine
Avatar
Samuel DEVULDER
Samuel DEVULDER a écrit :

extern void plot(int color, int y, int x);
// Ah ouais ok c'est donc un "y8" en fait.



J'imagine même pas la galère si le prototype avait été:

extern void plot(int, int, int);

C'est toujours une bonne idée de préciser le param et de lui donner un
nom explicite. Tiens dans ton code l'argument bp c'est quoi déjà? Il est
en entrée ou en sortie? La sous struct "in" qui regroupe les params en
entrées et "out" pour les sorties peuvent être utile aussi pour le coup
aussi tiens pour ta fonction.

sam.
Avatar
xtof pernod
Le 30/07/2010 09:39, Benoit Izac a fait rien qu'à écrire:
Bonjour,



Salut,


Je cherche à faire une fonction qui me permette de transformer un tampon
en lignes de façon à traiter ces dernières par la suite.

J'ai un bout de code qui fonctionne :
(...)
#define BS 10



Pas bien bon ^^ cf plus bas

#define LM 100

int get_line(const char *buffer, size_t *bp, size_t bs,
char *line, size_t *lp, size_t ls)
{
/* end of file? */
if (buffer[*bp] == EOF) {



Le test risque de sortir à vrai, mais pas de la manière dont tu t'y
attends:

(...)
int main(int argc, char *argv[])
{
int fd;
size_t r;
char buf[BS];
char line[LM];



Si c'est ton tableau de lignes à mémoriser, ce devrait être:
char *lines[LM]
sinon je vois pas bien comment tu compte faire après..

size_t bp = 0;
size_t lp = 0;
(...)



Le read() pose problème, mais je vois qu'on te l'a déja dit..

======================================================================= > PS : Désolé pour ceux qui ne sont pas sous Unix, j'ai bien essayé de



Non pas de pb, à vu de nez ça a l'air portable, s'il y a des erreurs,
ça sera plutôt la faute à ton compilateur.

faire la même chose avec fread() mais cette dernière ne me renvoie pas
la taille lu, ce qui pose un problème à la dernière lecture où r est la
plupart du temps différent de BS.



Vu que t'es sous Unix: man 3 fread
Je vois pas pourquoi ça ne marcherait pas dans ton cas..


Mon choix lorsque la taille de line est trop petite est discutable
(vaut-il mieux tronquer les lignes ?) mais ce n'est pas vraiment ce qui



Tronquer les lignes ? Au risque traiter que des données partielles ?

me pose problème.

Les questions :
* Ce code est-il correct (fonctionne-t-il dans tous les cas) ?



Non, donc..

* Est-il possible d'alléger le code (...



Oui ! Largement =)

* Existe-t-il une autre solution (fonction toute faite, autre
algorithme) ?



char *lines[LM];
char buf[BS];
FILE *fp = fdopen(fd);
int i=0;

while (fgets(buf, BS, fp))
lines[i++] = strdup(buf);

Verifier quand même que i ne dépasse pas LM..

Merci.



De rien,

--
christophe.
Avatar
xtof pernod
Le 30/07/2010 19:36, Benoit Izac a fait rien qu'à écrire:
Bonjour,

le 30/07/2010 à 19:02, Xavier Roche a écrit dans le message
<i2v0js$nu7$ :

#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>



Inutiles à priori ici.



read() est dans unistd.h, open() demande les deux autres.




c'est stat.h qui est de trop, alors.. bref, pas grave.


C'est justement là qu'est mon problème, je ne peux pas l'utiliser car je
n'ai pas un FILE* mais un tampon qui est mis à jour par une fonction
(exactement comme le read() ici).



Ah.

En fait, avec le read(), j'ai trouvé une astuce avec fdopen() mais elle
n'est pas utilisable avec la fonction que j'utilise (archive_read_data()
de la libarchive si ça en intéresse).




Bon, on reprend tout alors: tu veux une fonction qui te découpe
un tableau de caractères en entrée, en un tableau de lignes ?

Une ligne = 0..MAX car. terminée par 'n'.

Le tout fourni par:
size_t archive_read_data(struct archive *, void *buff, size_t len);


Dans ce cas, tu peux regarder du coté de strtok(3)

--
christophe.
Avatar
Benoit Izac
Bon je vais reprendre les explications car il semble que je n'ai pas été
clair. Soit le bout de code suivant :
======================================================================= #include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFSIZE 4096
#define LINE_MAX 2048

char *get_line(char *line, ...)
{
...
}

int main(int argc, char *argv[])
{
FILE *fp;
size_t r;
char buf[BUFSIZE];
char line[LINE_MAX];

if (argc != 2)
exit(1);

fp = fopen(argv[1], "r");
if (!fp)
exit(2);

while((r = fread(buf, 1, BS, fp)) > 0)
while (get_line(line, ...) != NULL)
if (strstr("blah", line)
printf("%sn", line);

fclose(fp);
return 0;
}
======================================================================= Le fread() n'est pas la fonction qui remplit buf, c'est juste là pour
l'exemple.

Comment écriveriez-vous get_line() ?

--
Benoit Izac
1 2 3 4 5