With the declaration of void get_line (char* filename)
, you can never make use of the line you read and store outside of the get_line
function because you do not return a pointer to line and do not pass the address of any pointer than could serve to make any allocation and read visible back in the calling function.
A good model (showing return type and useful parameters) for any function to read an unknown number of characters into a single buffer is always POSIX getline
. You can implement your own using either fgetc
of fgets
and a fixed buffer. Efficiency favors the use of fgets
only to the extent it would minimize the number of realloc
calls needed. (both functions will share the same low-level input buffer size, e.g. see gcc source IO_BUFSIZ
constant -- which if I recall is now LIO_BUFSIZE
after a recent name change, but basically boils down to an 8192
byte IO buffer on Linux and 512
bytes on windows)
So long as you dynamically allocate the original buffer (either using malloc
, calloc
or realloc
), you can read continually with a fixed buffer using fgets
adding the characters read into the fixed buffer to your allocated line and checking whether the final character is '\n'
or EOF
to determine when you are done. Simply read a fixed buffer worth of chars with fgets
each iteration and realloc
your line as you go, appending the new characters to the end.
When reallocating, always realloc
using a temporary pointer. That way, if you run out of memory and realloc
returns NULL
(or fails for any other reason), you won't overwrite the pointer to your currently allocated block with NULL
creating a memory leak.
A flexible implementation that sizes the fixed buffer as a VLA using either the defined SZINIT
for the buffer size (if the user passes 0
) or the size provided by the user to allocate initial storage for line
(passed as a pointer to pointer to char) and then reallocating as required, returning the number of characters read on success or -1
on failure (the same as POSIX getline
does) could be done like:
/** fgetline, a getline replacement with fgets, using fixed buffer.
* fgetline reads from 'fp' up to including a newline (or EOF)
* allocating for 'line' as required, initially allocating 'n' bytes.
* on success, the number of characters in 'line' is returned, -1
* otherwise
*/
ssize_t fgetline (char **line, size_t *n, FILE *fp)
{
if (!line || !n || !fp) return -1;
#ifdef SZINIT
size_t szinit = SZINIT > 0 ? SZINIT : 120;
#else
size_t szinit = 120;
#endif
size_t idx = 0, /* index for *line */
maxc = *n ? *n : szinit, /* fixed buffer size */
eol = 0, /* end-of-line flag */
nc = 0; /* number of characers read */
char buf[maxc]; /* VLA to use a fixed buffer (or allocate ) */
clearerr (fp); /* prepare fp for reading */
while (fgets (buf, maxc, fp)) { /* continuall read maxc chunks */
nc = strlen (buf); /* number of characters read */
if (idx && *buf == '\n') /* if index & '\n' 1st char */
break;
if (nc && (buf[nc - 1] == '\n')) { /* test '\n' in buf */
buf[--nc] = 0; /* trim and set eol flag */
eol = 1;
}
/* always realloc with a temporary pointer */
void *tmp = realloc (*line, idx + nc + 1);
if (!tmp) /* on failure previous data remains in *line */
return idx ? (ssize_t)idx : -1;
*line = tmp; /* assign realloced block to *line */
memcpy (*line + idx, buf, nc + 1); /* append buf to line */
idx += nc; /* update index */
if (eol) /* if '\n' (eol flag set) done */
break;
}
/* if eol alone, or stream error, return -1, else length of buf */
return (feof (fp) && !nc) || ferror (fp) ? -1 : (ssize_t)idx;
}
(note: since nc
already holds the current number of characters in buf
, memcpy
can be used to append the contents of buf
to *line
without scanning for the terminating nul-character again) Look it over and let me know if you have further questions.
Essentially you can use it as a drop-in replacement for POSIX getline
(though it will not be quite as efficient -- but isn't not bad either)