thecodingidiot.com

The ReaderSetup

Setup

No new tools are needed for this chapter. The toolchain from f05/00 covers everything: gcc, make, valgrind.

Start from c02

tci_getline extends libtci — the static library built in c01 and extended in c02. Create a new working directory from your c02 project:

cp -r ~/c02-practice ~/c03-practice
cd ~/c03-practice

If you are starting fresh or want the reference version, clone the c02 companion repo and copy the solution:

git clone https://github.com/thecodingidiot-com/c02-the-voice.git
cp -r c02-the-voice/solution ~/c03-practice
cd ~/c03-practice

Verify the library builds:

make re

You should see ar rcs libtci.a complete with no warnings. Thirty object files, one archive.


Add the tci_getline stub

Create tci_getline.c with a function that compiles but returns nothing yet:

#include "libtci.h"
#include <unistd.h>
#include <stdlib.h>
 
char    *tci_getline(int fd)
{
    (void)fd;
    return (NULL);
}

<unistd.h> provides read(). <stdlib.h> provides free() and malloc() — both will be needed once the implementation is in place.


Set up libtciutil

tciu_split is not a reimplementation of anything in the standard library — there is no split in libc or POSIX. It is an original utility function. That distinction matters: libtci exists to mirror the standard library, so libtci is not the right place for it.

libtciutil is the second library. Its archive is libtciutil.a; its header is libtciutil.h; every function uses the tciu_ prefix. libtciutil holds utility functions with no standard equivalent. It depends on libtci — tciu_ functions may call tci_* functions freely — but libtci does not depend on libtciutil.

Create libtciutil.h:

#ifndef LIBTCIUTIL_H
# define LIBTCIUTIL_H
 
# include "libtci.h"
 
char    **tciu_split(char const *s, char sep);
 
#endif

Create tciu_split.c with a stub that compiles but returns nothing yet:

#include "libtciutil.h"
 
char    **tciu_split(char const *s, char sep)
{
    (void)s;
    (void)sep;
    return (NULL);
}

Update the Makefile

Add tci_getline.c to libtci's SRCS and introduce a second archive target for libtciutil:

NAME        = libtci.a
UTIL        = libtciutil.a
CC          = gcc
CFLAGS      = -Wall -Wextra -g -std=c99
AR          = ar rcs
 
SRCS        = tci_memset.c tci_memcpy.c tci_memmove.c tci_memchr.c tci_bzero.c \
              tci_isascii.c tci_isalpha.c tci_isdigit.c tci_isalnum.c \
              tci_isspace.c tci_isupper.c tci_islower.c tci_isprint.c \
              tci_toupper.c tci_tolower.c \
              tci_strlen.c tci_strcpy.c tci_strncpy.c tci_strlcpy.c tci_strlcat.c \
              tci_strcmp.c tci_strncmp.c tci_strchr.c tci_strrchr.c tci_strnstr.c \
              tci_atoi.c \
              tci_calloc.c tci_strdup.c tci_strndup.c \
              tci_printf.c \
              tci_getline.c
 
UTIL_SRCS   = tciu_split.c
 
OBJS        = $(SRCS:.c=.o)
UTIL_OBJS   = $(UTIL_SRCS:.c=.o)
 
.PHONY: all clean fclean re
 
all: $(NAME) $(UTIL)
 
$(NAME): $(OBJS)
	$(AR) $(NAME) $(OBJS)
 
$(UTIL): $(UTIL_OBJS)
	$(AR) $(UTIL) $(UTIL_OBJS)
 
%.o: %.c libtci.h
	$(CC) $(CFLAGS) -c $< -o $@
 
tciu_split.o: tciu_split.c libtciutil.h libtci.h
	$(CC) $(CFLAGS) -c $< -o $@
 
clean:
	rm -f $(OBJS) $(UTIL_OBJS)
 
fclean: clean
	rm -f $(NAME) $(UTIL)
 
re: fclean all

Update libtci.h

Add the tci_getline declaration:

char    *tci_getline(int fd);

Compile with BUFFER_SIZE

tci_getline reads in chunks whose size is controlled by a compile-time define. The -D flag passes a value to the preprocessor:

gcc -Wall -Wextra -g -std=c99 -D BUFFER_SIZE=32 -c tci_getline.c

Add it to CFLAGS in the Makefile so make re uses it automatically:

CFLAGS  = -Wall -Wextra -g -std=c99 -D BUFFER_SIZE=32

The tester recompiles with multiple values — including BUFFER_SIZE=1, the hardest case — so the define must remain a compile-time constant. Never replace it with a hardcoded literal inside the function.

Run make re. Thirty-one libtci objects (libtci.a) and one libtciutil object (libtciutil.a), no warnings. Both stubs compile but do nothing — that changes on the next page.


Get the tester

Clone the companion repo and copy test.sh into your working directory:

git clone https://github.com/thecodingidiot-com/c03-the-reader.git
cp c03-the-reader/test.sh ~/c03-practice/

You will run bash test.sh after each implementation page. Leave the clone in place — you only need to do this once.


What tci_getline does

A text file is a sequence of characters. Lines are separated by the newline character '\n'. Each line ends where the next '\n' appears — or at the end of the file if there is no final newline.

tci_getline(fd) reads from file descriptor fd and returns one line per call. Each call picks up exactly where the previous one left off. The returned string includes the trailing '\n' if the line had one. When the file is fully consumed, it returns NULL. The caller owns the returned string and must free it.

The function must work correctly regardless of how BUFFER_SIZE is set — even if BUFFER_SIZE is 1 and every read() call returns exactly one byte. The next page shows why.