get_next_line
ProjectM1
Read a file descriptor line by line with a static buffer.
Requires
Unlocks
Guide
get_next_line is a milestone M1 project in C, done solo. You write a function, get_next_line(int fd), that returns the next line of a file descriptor each time it is called, including the trailing newline when there is one, and NULL at the end of the file or on error. It reads with a buffer whose size is chosen at compile time with -D BUFFER_SIZE=n. The project is small in code and large in ideas: static variables, buffered reads and careful memory ownership.
What you will learn
- How
readworks: partial reads, return values, end of file and errors - Static variables and why they survive between calls
- Building a string incrementally without leaking the previous version
- Thinking about memory ownership: who allocates, who frees, and when
- Compile-time configuration with
-Dmacros - (Bonus) Keeping independent state for several file descriptors at once
Before you start
- Previous nodes: Libft. The subject forbids linking libft, so copy the few helpers you need into
get_next_line_utils.c. - Tools:
cc,norminette,valgrind. - Read
man 2 readandman 2 open. - Files to deliver:
get_next_line.c,get_next_line_utils.c,get_next_line.h, plus the_bonusversions for the bonus.
Plan of attack
- Day 1: on paper, describe the state you need between calls: the leftover bytes after the last newline. Everything else is temporary.
- Day 1: write the helpers:
strlen,strchr, astrjointhat frees its first argument, and asubstr. - Day 2: write the read loop: read
BUFFER_SIZEbytes into a heap buffer, append to the leftover, stop when the leftover contains a newline orreadreturns 0 or -1. - Day 2: split the leftover into the line to return and the new leftover. Free the old leftover.
- Day 3: test with
BUFFER_SIZE1, 42, 10000000, and with files that end with and without a newline, an empty file, a file of only newlines, and standard input. - Day 3: test the error paths: an invalid fd, a closed fd,
readreturning -1 halfway. Free the leftover and returnNULL. - Day 4 (bonus): keep one leftover per fd, using a static array indexed by fd (bounded by a reasonable limit) and only one static variable.
- Day 5: valgrind on every case, norminette, a tester.
char *get_next_line(int fd)
{
static char *stash;
char *line;
if (fd < 0 || BUFFER_SIZE <= 0)
return (NULL);
stash = fill_stash(fd, stash);
if (!stash)
return (NULL);
line = extract_line(stash);
stash = keep_rest(stash);
return (line);
}
Common pitfalls
- Leaks of the leftover at end of file, or when an error happens after a successful read.
- Returning an empty string instead of
NULLat end of file. - Allocating a buffer on the stack with a huge
BUFFER_SIZE: use the heap. - Reading the whole file at once instead of stopping at the first newline.
- Using
lseekor global variables: forbidden. - Not handling
BUFFER_SIZEvalues like 0 or very large ones without crashing. - In the bonus, more than one static variable.
How it is evaluated
- Peer evaluation then Moulinette. The evaluator checks norminette, the absence of libft and of globals, and compiles with several
BUFFER_SIZEvalues. - Tests cover normal files, empty files, long lines, lines without a final newline, standard input and invalid file descriptors.
- Leaks are checked with valgrind or
leaks. A single leak fails the project. - The bonus is graded only if the mandatory part is perfect: alternate reads between several fds and check each returns its own lines.
- You must explain the static variable and the lifecycle of each allocation.