thecodingidiot.com

The ToolkitThe Library

The Library

You have been running ar rcs libtci.a ... at the end of every make. It is time to understand what that command produces and why it is called a library.

What a static library is

A static library is an archive of compiled object files. The ar command — the archiver — bundles .o files into a single .a file, with an index that maps function names to their locations inside the archive. When you link a program against libtci.a, the linker reads that index, finds the object files containing the functions your program calls, and pulls them into the executable.

"Static" means the library's code is copied into the executable at link time. Every program that links against libtci.a gets its own copy of the relevant tci_* functions baked in. The .a file is only needed at compile time; the executable carries everything it needs.

The alternative is a shared library (.so on Linux), where the code stays in the .so file and multiple programs share a single copy at runtime. Shared libraries are what glibc uses — the libc.so.6 you see in /usr/lib. They are not covered in this chapter; the distinction matters more when you are shipping software than when you are learning to write C.

The ar flags

ar rcs is three flags in one:

  • r — insert each .o file into the archive, replacing an existing member of the same name if present
  • c — create the archive file if it does not exist, silently
  • s — write a symbol index into the archive (equivalent to running ranlib separately)

Without the s flag, the linker could not find functions in the archive quickly. Older Makefiles ran ranlib as a separate step after ar rc. The s flag combines both into one command.

The complete libtci.h

This is a good moment to look at the complete header. Open libtci.h and confirm it contains all of the following, in order:

#ifndef LIBTCI_H
#define LIBTCI_H
 
#include <stddef.h>
 
/* memory */
void    *tci_memset(void *s, int c, size_t n);
void    *tci_memcpy(void *dst, const void *src, size_t n);
void    *tci_memmove(void *dst, const void *src, size_t n);
void    *tci_memchr(const void *s, int c, size_t n);
void    tci_bzero(void *s, size_t n);
void    *tci_calloc(size_t count, size_t size);
 
/* character classification */
int     tci_isascii(int c);
int     tci_isalpha(int c);
int     tci_isdigit(int c);
int     tci_isalnum(int c);
int     tci_isspace(int c);
int     tci_isupper(int c);
int     tci_islower(int c);
int     tci_isprint(int c);
int     tci_toupper(int c);
int     tci_tolower(int c);
 
/* strings */
size_t  tci_strlen(const char *s);
char    *tci_strcpy(char *dst, const char *src);
char    *tci_strncpy(char *dst, const char *src, size_t n);
size_t  tci_strlcpy(char *dst, const char *src, size_t size);
size_t  tci_strlcat(char *dst, const char *src, size_t size);
int     tci_strcmp(const char *s1, const char *s2);
int     tci_strncmp(const char *s1, const char *s2, size_t n);
char    *tci_strchr(const char *s, int c);
char    *tci_strrchr(const char *s, int c);
char    *tci_strdup(const char *s);
char    *tci_strndup(const char *s, size_t n);
char    *tci_strnstr(const char *haystack, const char *needle,
        size_t len);
int     tci_atoi(const char *str);
 
#endif

If any declarations are missing, add them now.

The complete Makefile

The SRCS line should list every .c file you have written. A clean version with all source files:

NAME    = libtci.a
CC      = gcc
CFLAGS  = -Wall -Wextra -g -std=c99
AR      = ar
ARFLAGS = rcs
 
SRCS    = tci_memset.c tci_memcpy.c tci_memmove.c tci_memchr.c tci_bzero.c \
          tci_calloc.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_strdup.c tci_strndup.c tci_strnstr.c tci_atoi.c
 
OBJS    = $(SRCS:.c=.o)
 
all: $(NAME)
 
$(NAME): $(OBJS)
	$(AR) $(ARFLAGS) $(NAME) $(OBJS)
 
%.o: %.c libtci.h
	$(CC) $(CFLAGS) -c $< -o $@
 
clean:
	rm -f $(OBJS)
 
fclean: clean
	rm -f $(NAME)
 
re: fclean all
 
.PHONY: all clean fclean re

Run make re to do a clean rebuild from scratch:

make re

You should see 29 compile lines and one ar line, all clean.

The lib* naming rule

Unix has a convention for naming libraries: a library called foo lives in libfoo.a and is linked with -lfoo. The lib prefix and .a extension belong to the filename; the name you give the linker is only the middle part.

That is why libtci.a is linked with -ltci, not -llibtci. The gcc flag -L. tells the linker to look in the current directory; -ltci says which library to find there. The linker applies the rule automatically: it looks for libtci.a (or libtci.so on Linux).

Every library on the system follows this rule. -lm links libm.a (the math library). -lpthread links libpthread.a. Knowing the rule lets you read a link command and immediately name the library file behind each flag.

Linking against the library

Write a small program that uses several libtci functions to confirm the library links correctly. Create try.c:

#include "libtci.h"
#include <stdlib.h>
#include <stdio.h>
 
int main(void)
{
    char    *copy;
    char    *found;
    int     n;
 
    copy = tci_strdup("hello, world");  /* heap copy — must be freed */
    printf("%s\n", copy);
    free(copy);
 
    found = tci_strnstr("one two three", "two", 13);  /* pointer into the literal, not a new allocation */
    if (found)
        printf("found: %s\n", found);
 
    n = tci_atoi("  -7  ignored");  /* stops at the space after -7; trailing text is not an error */
    printf("parsed: %d\n", n);
    return (0);
}

Compile and link:

gcc -Wall -Wextra -g -std=c99 try.c -L. -ltci -I. -o try

Run it:

./try

Expected output:

hello, world
found: two three
parsed: -7

Run it under valgrind:

valgrind --leak-check=full ./try

Zero leaks. tci_strdup allocates and free(copy) releases it. tci_strnstr and tci_atoi allocate nothing — they return a pointer into the original string and a plain int respectively.

Delete try.c and try when you are satisfied.

What nm tells you

nm lists the symbols in a library. Run it on libtci.a:

nm libtci.a

You will see a list of function names tagged with T (defined in the text section — meaning they are implemented functions) and U (undefined — meaning they call something from another library, like malloc). Every tci_* function should appear as T. The U entries for malloc and free are resolved when you link a program against both libtci.a and the C standard library.

The standard library is linked automatically by gcc. You do not need to write -lc because gcc adds it by default.