Herranz

Sesión 09: Leer línea

En muchas ocasiones y en muchos ejercicios prácticos y teóricos es necesario leer líneas de un fichero. En general siempre aparece la coletilla “se asume que las líneas no superan (por ejemplo) los 2046 caracteres”. Eso como veremos a continuación simplifica la lectura de cada línea. En este taller veremos como podemos leer líneas de caracteres donde el número de caracteres no está limitado a priori. Este es un problema muy interesante.

Leer líneas con un límite

 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
fgetc(3)               Library Functions Manual               fgetc(3)

NAME
       fgetc,  fgets,  getc, getchar, ungetc - input of characters and
       strings

LIBRARY
       Standard C library (libc, -lc)

SYNOPSIS
       #include <stdio.h>

       int fgetc(FILE *stream);
       int getc(FILE *stream);
       int getchar(void);

       char *fgets(char s[restrict .size], int size, FILE *restrict stream);

       int ungetc(int c, FILE *stream);

DESCRIPTION
       fgetc() reads ...

       getc() is equivalent to fgetc() ...

       getchar() is equivalent to getc(stdin).

       fgets()  reads  in  at  most one less than size characters from
       stream and stores them into the buffer pointed to by s.   Read‐
       ing  stops after an EOF or a newline.  If a newline is read, it
       is stored into the buffer.  A terminating null byte  ('\0')  is
       stored after the last character in the buffer.

       ungetc()  pushes c back to stream...

       ...

RETURN VALUE
       fgetc(),  getc(), and getchar() return the character read ...

       fgets() returns s on success, and NULL on error or when end  of
       file occurs while no characters have been read.

       ungetc() returns c on success, or EOF on error.

ATTRIBUTES
       For  an  explanation of the terms used in this section, see at‐
       tributes(7).
       ┌───────────────────────────────────┬───────────────┬─────────┐
       │ Interface                         │ Attribute     │ Value   │
       ├───────────────────────────────────┼───────────────┼─────────┤
       │ fgetc(), fgets(), getc(),         │ Thread safety │ MT-Safe │
       │ getchar(), ungetc()               │               │         │
       └───────────────────────────────────┴───────────────┴─────────┘

STANDARDS
       C11, POSIX.1-2008.

HISTORY
       POSIX.1-2001, C89.

NOTES
       It is not advisable to mix calls to input  functions  from  the
       stdio  library with low-level calls to read(2) for the file de‐
       scriptor associated with the input stream; the results will  be
       undefined and very probably not what you want.

SEE ALSO
       read(2),  write(2),  ferror(3), fgetwc(3), fgetws(3), fopen(3),
       fread(3), fseek(3), getline(3), gets(3), getwchar(3),  puts(3),
       scanf(3), ungetwc(3), unlocked_stdio(3), feature_test_macros(7)

Linux man-pages 6.7           2023-10-31                      fgetc(3)
1
 char *fgets(char *s, int size, FILE *stream);

Longitud de la línea

Vamos a suponer que esos 2046 caracteres no incluyen el cambio de línea (\n). Como fgets, según el manual, incluiría el cambio de línea, necesitamos un array de caracteres de 2048 caracteres: los 2046 exigidos, más el cambio de línea, más el caracter de terminación (\0). Por lo tanto tendríamos algo como esto:

1
2
char linea[2048];
fgets(linea, 2048, ...);

Si en vez de usar memoria estática queremos usar memoria dinámica podríamos hacer:

1
2
3
4
5
char *linea = malloc(2048 * sizeof(char));
fgets(linea, 2048, ...);
...
/* ¡Hay que plantearse liberar la memoria una vez usada! */
free(linea);

NOTA: para probar tus ejemplos quizás prefieras usar un tamaño de línea máximo de 10 caracters y así puedes probar fácilmente qué ocurre en los límites.

¿Qué pasaría si le pasas un array de caracters demasiado pequeño?

Piensa en ello, imagina que se hace esta llamada por error y la línea tiene más de 1024 caracteres:

1
2
char linea[1024];
fgets(linea, 2048, ...);

FILE *

Para poder disponer de un dato de tipo FILE * tenemos dos opciones:

1
2
3
       #include <stdio.h>

       FILE *fopen(char *pathname, char * mode);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
stdin(3)               Library Functions Manual               stdin(3)

NAME
       stdin, stdout, stderr - standard I/O streams

LIBRARY
       Standard C library (libc, -lc)

SYNOPSIS
       #include <stdio.h>

       extern FILE *stdin;
       extern FILE *stdout;
       extern FILE *stderr;

...

Implementar y probar la función leer_linea

Tu primera tarea consiste en hacer un programa principal leer_linea1.c con un main que lea una línea usando una función que tendrás que implementar en el mismo fichero con esta cabecera:

1
2
3
4
5
6
/*
 * leer_linea lee una línea de un máximo de 2046 caracteres de la
 * entrada estándar y devuelve un nuevo string (internamente debería
 * hacer un `malloc`) que incluye el cambio de línea.
 */
extern char *leer_linea();

Tu main simplemente tiene que imprimir la longitud de la línea tras haberla leído y cargado en un string, algo como esto:

1
2
3
4
5
6
char *s;

s = leer_linea();
printf("%lu\n", strlen(s));
/* Importante: liberar la memoria cuando ya no se va a usar */
free(s);

Observar free

¿Y si la longitud de la línea es menor que la memoria reservada?

De alguna forma estamos desperdiciando memoria. Curiosamente hay una función que te permite reducir la memoria que hayas pedido al sistema operativo a la que realmente necesitas. ¿Se te ocurre alguna forma de hacer un malloc para 2048 y realocar memoria si no necesitas 2048?

Leer líneas sin un límite

Ahora empieza la parte complicada. Tienes que hacer un programa como el anterior leer_linea2.c pero esta vez la función leer_linea no tiene la precondición de “un máximo de 2046 caracteres”.

Esto complica mucho las cosas como verás. Puedes intentarlo tú misma antes de mirar las siguientes pistas:

Primeras pistas

Segundas pistas

1
 fgets(&s[9], 10, stdin);
1
 fgets(s + 9, 10, stdin);