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-practiceIf 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-practiceVerify the library builds:
make reYou 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);
#endifCreate 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 allUpdate 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.cAdd it to CFLAGS in the Makefile so make re uses it automatically:
CFLAGS = -Wall -Wextra -g -std=c99 -D BUFFER_SIZE=32The 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.