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.ofile into the archive, replacing an existing member of the same name if presentc— create the archive file if it does not exist, silentlys— write a symbol index into the archive (equivalent to runningranlibseparately)
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);
#endifIf 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 reRun make re to do a clean rebuild from scratch:
make reYou 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 tryRun it:
./tryExpected output:
hello, world
found: two three
parsed: -7Run it under valgrind:
valgrind --leak-check=full ./tryZero 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.aYou 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.