This project has been created as part of the 42 curriculum by itanvuia
Read a line from a file descriptor, one line at a time
get_next_line (GNL) is a 42 School project that implements a function to read and return one line at a time from a file descriptor. This project teaches advanced C concepts including static variables, dynamic memory allocation, buffer management, and efficient file I/O operations.
The Challenge: Create a function that can read any valid file line by line, handling variable buffer sizes and multiple file descriptors, without knowing the file size in advance.
Why this matters:
- Master static variables for maintaining state between function calls
- Understand file descriptors and low-level I/O operations
- Implement efficient buffer management and memory handling
- Build a reusable utility function for future projects
char *get_next_line(int fd);Parameters:
fd- The file descriptor to read from
Return Value:
- Returns the line that was read (including the terminating
\ncharacter, except at EOF) - Returns
NULLif there is nothing else to read or an error occurred
- ✅ Reads one line at a time from any file descriptor
- ✅ Returns the line including the newline character (
\n) - ✅ Handles files, standard input, and any valid file descriptor
- ✅ Works with any buffer size (defined at compile time)
- ✅ Uses static variables to maintain reading state between calls
- ✅ Properly handles EOF (end of file)
- ✅ Memory-safe implementation with no leaks
Language: C
Allowed functions: read, malloc, free
Forbidden: libft functions, global variables, lseek
- Static Variables - Preserving leftover data between function calls
- Buffer Management - Efficient reading with configurable BUFFER_SIZE
- Dynamic Memory - Allocating and growing strings as needed
- File Descriptors - Working with Unix file I/O
- Edge Cases - EOF, empty lines, very large lines, read errors
get_next_line/
├── get_next_line.c # Main function implementation
├── get_next_line_utils.c # Helper functions (string manipulation)
├── get_next_line.h # Header file with prototypes
└── bonus/
├── get_next_line_bonus.c # Main bonus function implementation
├── get_next_line_utils_bonus.c # Helper functions (string manipulation)
└── get_next_line_bonus.h # Bonus header file with prototypes
1. Read BUFFER_SIZE bytes from file descriptor
2. Store leftover data in static variable
3. Search for newline character ('\n')
4. Return complete line (up to and including '\n')
5. Keep remaining data in static buffer for next call
6. Repeat until EOF
The buffer size is defined at compile time using the -D flag:
# Compile with buffer size of 42
cc -Wall -Wextra -Werror -c -D BUFFER_SIZE=42 get_next_line.c get_next_line_utils.c -o gnl_test
# Common buffer sizes for testing
cc -D BUFFER_SIZE=1 ... # Minimum - stress test
cc -D BUFFER_SIZE=32 ... # Standard
cc -D BUFFER_SIZE=1024 ... # Larger buffer
cc -D BUFFER_SIZE=9999999 # Edge case testing# Compile with buffer size of 42
cc -Wall -Wextra -Werror -D BUFFER_SIZE=42 get_next_line_bonus.c get_next_line_utils_bonus.c -o gnl_test
# Common buffer sizes for testing
cc -D BUFFER_SIZE=1 ... # Minimum - stress test
cc -D BUFFER_SIZE=32 ... # Standard
cc -D BUFFER_SIZE=1024 ... # Larger buffer
cc -D BUFFER_SIZE=9999999 # Edge case testing#include "get_next_line.h"
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
int fd;
char *line;
// Open a file
fd = open("test.txt", O_RDONLY);
if (fd == -1)
return (1);
// Read file line by line
while ((line = get_next_line(fd)) != NULL)
{
printf("%s", line);
free(line); // Don't forget to free!
}
close(fd);
return (0);
}#include "get_next_line.h"
#include <stdio.h>
int main(void)
{
char *line;
printf("Enter text (Ctrl+D to end):\n");
// Read from stdin (file descriptor 0)
while ((line = get_next_line(0)) != NULL)
{
printf("You entered: %s", line);
free(line);
}
return (0);
}For reading from multiple files simultaneously, you would need to call get_next_line() multiple times with different file descriptors:
#include "get_next_line_bonus.h"
#include <fcntl.h>
int main(void)
{
int fd1, fd2;
char *line1, *line2;
fd1 = open("file1.txt", O_RDONLY);
fd2 = open("file2.txt", O_RDONLY);
// Read one line from each file
line1 = get_next_line(fd1);
printf("File 1: %s", line1);
free(line1);
line2 = get_next_line(fd2);
printf("File 2: %s", line2);
free(line2);
close(fd1);
close(fd2);
return (0);
}# Create test file
echo -e "Line 1\nLine 2\nLine 3" > test.txt
# Compile and run
cc -D BUFFER_SIZE=32 get_next_line.c get_next_line_utils.c main.c
./a.out test.txt# Compile with debug symbols
cc -g -D BUFFER_SIZE=32 get_next_line.c get_next_line_utils.c main.c
# Check for leaks
valgrind --leak-check=full --show-leak-kinds=all ./a.out test.txt- Tripouille/gnlTester - Comprehensive automated tester
- Mazoise/42TESTERS-GNL - Complete tester suite
- charMstr/GNL_lover - Combinatory tricky tests
// Empty file
// File with only newlines
// File with no newline at end
// Very long lines (10,000+ characters)
// Binary files
// BUFFER_SIZE = 1 (character by character)
// BUFFER_SIZE = 9999999 (very large)
// Invalid file descriptor (-1, 1000, etc.)
// Read errors (permissions, closed FD)
// Sequential reads from multiple file descriptorsThe selected algorithm utilizes a Read-Accumulate-Extract strategy to handle variable line lengths and buffer sizes efficiently:
Accumulation: The read_and_accumulate() function reads data in BUFFER_SIZE chunks and appends it to a static "backup" string. This ensures that even if a read spans multiple lines, no data is lost between calls.
Line Search: The process continues until a newline (\n) is found in the buffer or the end of the file (EOF) is reached.
Extraction: The extract() function identifies the exact position of the newline, creates a new string for the current line, and updates the static backup to store only the "leftover" characters for the next call.
Efficiency: By using a static array for the bonus part, the function can maintain 1024 unique "backups" for different file descriptors simultaneously using only one static variable.
-
Static Variable Management
- Understanding how static variables persist between function calls
- Properly managing leftover data from previous reads
- Maintaining state for a single file descriptor efficiently
-
Buffer Size Optimization
- Making the function work with any buffer size (1 to 9999999)
- Handling cases where a line is smaller or larger than BUFFER_SIZE
- Efficient string concatenation when line spans multiple reads
-
Memory Management
- Preventing memory leaks in all scenarios
- Proper allocation and deallocation of dynamic strings
- Managing memory when read() fails mid-operation
-
Edge Case Handling
- Files ending without newline character
- Empty lines (just '\n')
- Binary files and special characters
- Multiple reads from different file descriptors sequentially
-
Read Function Mastery
- Understanding read() return values (>0, 0, -1)
- EOF detection and handling
- Error management when read fails
- Advanced C memory management - Dynamic allocation, reallocation strategies, leak prevention
- Static variables in depth - Lifetime, scope, and practical applications
- File I/O operations - Low-level file reading, file descriptors, Unix I/O
- State management - Maintaining context between function calls
- Robust error handling - Graceful failure and resource cleanup
- Performance optimization - Buffer management trade-offs
| BUFFER_SIZE | Read Calls | Performance | Use Case |
|---|---|---|---|
| 1 | Many | Slowest | Testing edge cases |
| 32-128 | Moderate | Balanced | Standard usage |
| 1024-4096 | Few | Fastest | Large files |
| 9999999 | Minimal | Memory intensive | Stress testing |
- Minimize malloc() calls - Reuse buffers when possible
- Efficient string operations - Avoid unnecessary copying
- Smart read() strategy - Balance between syscalls and memory usage
- Early returns - Check error conditions first
Grade: 125/125 ✅
Bonus: [YES]
Evaluation Date: [02/03/2026]
Peer Review Highlights:
- Clean, readable code following 42 norm
- No memory leaks detected across all test cases
- Works perfectly with BUFFER_SIZE from 1 to 9999999
- Efficient implementation with minimal overhead
- Excellent handling of edge cases
This project builds upon:
- Libft - String manipulation functions were useful here
This project is part of the 42 School curriculum. Feel free to reference this code for learning purposes, but please complete your own 42 projects independently to get the full educational benefit.
This is a completed school project, but feedback and suggestions are always welcome! If you found this implementation helpful or have questions, feel free to reach out.
Author: Alex Tanvuia (Ionut Tanvuia)
42 Login: itanvuia
School: 42 London
Project Completed: [December 2025]
Part of my journey through 42 School's peer-learning curriculum. Check out my other projects on my GitHub profile!