get_next_line is a low-level C function that reads a file descriptor one line at a time.
Each call returns the next available line, including the terminating newline character when one is present. The function preserves unread data between calls by using static storage.
This project was completed as part of the 42 / 1337 Common Core.
char *get_next_line(int fd);The function repeatedly reads data from the supplied file descriptor until:
- a newline character is found;
- the end of the file is reached;
- or a read or allocation error occurs.
Unread data is preserved inside a static variable so that the following call can continue from the correct position.
File descriptor
│
▼
read() into buffer
│
▼
append buffer to stash
│
├── newline found ──► extract and return one line
│
└── no newline ─────► continue reading
│
▼
preserve remainder
The function returns:
- the next line read from the file descriptor;
- the final line even when it does not end with
\n; NULLwhen no more data is available;NULLwhen an error occurs.
The caller is responsible for freeing every returned line.
A static variable is used to retain unread data between function calls.
For example, when the file contains:
Hello, 42!
This is get_next_line.
The first read may retrieve more than one line. The function returns:
Hello, 42!\n
and preserves:
This is get_next_line.
for the following call.
The mandatory implementation supports reading from one file descriptor at a time.
Typical files:
get_next_line.c
get_next_line_utils.c
get_next_line.h
The bonus implementation supports multiple file descriptors simultaneously.
Each descriptor keeps its own independent stash, allowing calls such as:
line1 = get_next_line(fd1);
line2 = get_next_line(fd2);
line3 = get_next_line(fd1);Typical bonus files:
get_next_line_bonus.c
get_next_line_utils_bonus.c
get_next_line_bonus.h
The amount of data read during each call to read() is controlled by the BUFFER_SIZE macro.
Example:
cc -Wall -Wextra -Werror -D BUFFER_SIZE=42 \
main.c get_next_line.c get_next_line_utils.cThe implementation should work with different values, including:
BUFFER_SIZE=1
BUFFER_SIZE=42
BUFFER_SIZE=1024
BUFFER_SIZE=9999
A small buffer causes more calls to read(), while a larger buffer may retrieve multiple lines at once.
Example program:
#include "get_next_line.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int fd;
char *line;
fd = open("example.txt", O_RDONLY);
if (fd < 0)
return (1);
line = get_next_line(fd);
while (line)
{
printf("%s", line);
free(line);
line = get_next_line(fd);
}
close(fd);
return (0);
}Compile it with:
cc -Wall -Wextra -Werror \
-D BUFFER_SIZE=42 \
main.c \
get_next_line.c \
get_next_line_utils.c \
-o gnlRun:
./gnlThe implementation should correctly handle:
- empty files;
- files containing only one character;
- files containing only newline characters;
- lines longer than
BUFFER_SIZE; - files without a final newline;
- very small buffer sizes;
- very large buffer sizes;
- invalid file descriptors;
- closed file descriptors;
- read errors;
- repeated calls after reaching the end of the file;
- multiple file descriptors in the bonus version.
All dynamically allocated memory must be released correctly.
The implementation must avoid:
- memory leaks;
- double frees;
- invalid reads;
- invalid writes;
- losing the saved remainder;
- returning pointers to temporary buffers.
A returned line remains owned by the caller:
line = get_next_line(fd);
if (line)
{
printf("%s", line);
free(line);
}A basic test can read and display an entire file:
#include "get_next_line.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
int fd;
char *line;
if (argc != 2)
return (1);
fd = open(argv[1], O_RDONLY);
if (fd < 0)
return (1);
while ((line = get_next_line(fd)) != NULL)
{
printf("%s", line);
free(line);
}
close(fd);
return (0);
}Useful tools include:
valgrind --leak-check=full ./gnl example.txtRecommended tests:
empty.txt
one_character.txt
one_line.txt
multiple_lines.txt
long_line.txt
no_final_newline.txt
only_newlines.txt
The implementation can be divided into three main stages:
Read chunks into a temporary buffer and append them to the saved stash until a newline or EOF is found.
Create a new string containing the first complete line from the stash.
Remove the returned line from the stash and keep the remaining characters for the next call.
This project provided practical experience with:
- file descriptors;
- the
read()system call; - static variables;
- dynamic memory allocation;
- string manipulation;
- persistent state between function calls;
- memory ownership;
- defensive error handling;
- reading multiple file descriptors;
- testing with different compilation-time configurations.
Walid Krati
- GitHub: @wkratos
This repository contains my implementation of the 42 get_next_line project. It is intended as a portfolio and educational reference. Students are encouraged to understand and implement the project independently.