This tutorial will cover creating a static library using the command line with the GNU C/C++ compiler. We will use C++ as the primary language although a slight modification in the outlined steps can be used with the C language. Unlike most tutorials which use trivial and relatively useless functions to demonstrate building libraries, we will implement a fairly useful std::string function that is bizarrely missing from the STL - the string split (or tokenize) function and build a static library from that.

TL;DR

# Create object files from source
g++ -c *.cpp

# Create the static library from the object files
ar -cvq libmylib.a *.o

# Build and link
g++ -o myapp *.cpp libmylib.a

Let’s first address the elephant in the room:

Why build a library at all?

We build libraries to allow code reuse by packaging commonly reused code into a library for programmers to share. This speeds up development time considerably in a well designed library. As an example, the standard C library on *nix and Linux systems (libc.a) is a static library that can be found in the /usr/lib directory of your computer. If you built 10 C applications that use the C standard library (which is almost all user space applications), then each of the resulting binaries will include a copy of the code contained in libc.a. The presence of libc.a allows you to use all of the C standard functions without having to reimplement them in each program. Moreover, building the library as a static library prevents you from having to package libc.a with every program that uses it. More on that below…

What is a static library?

A static library is a collection of object files that the compiler collects into a single structure so that it can be statically linked into an executable; an object file is compiled code that the linker links in to the final executable. On *nix and MacOS systems, static libraries usually have a .a file extension; Windows systems usually have a .lib extension. Statically linking an object file (or a static library) means that the object files are directly copied into the executable at compile time (hence the word “static” in “static library”). There are a few consequences to this:

  1. Resulting binaries are larger since the code from static libraries is now directly included into the binary. Depending on the number of static libraries linked in and their total size, this can cause the compiler to create a larger executable. Larger executables take up more memory on disk but more importantly, larger executables can be slower than smaller ones.
  2. Deploying applications built with static libraries is easier since we do not have to distribute the libraries alongside the built binary since they’re already linked in. This is not the case with dynamic libraries.
  3. Link times can be significantly increased if there are many static libraries to link in.

Preparation

Now that we’ve covered the background, we will need to set up a few things to get started. First, make sure you have installed the GCC C/C++ compiler; this usually comes prepackaged into most Linux distros. To check if you have this compiler, type the following into a terminal:

whereis g++

If you get no output, then you must install the compiler using your package manager. Now let’s create a few directories where we will store our header files, source files, libraries, and built executables. I decided to create these in my home directory but you can obviously create them anywhere. Go to your home directory and type:

mkdir include lib source bin

We will use the include directory to store our header files, the lib directory to store the built static libraries, source directory to store our source implementation files, and bin directory to store our built executable.

Let’s write some code!

The std::string class in the STL is a great class for performing common string operations yet it is also one of the most lacking classes when compared to its counterparts in other languages. The class does not even have a split()function to split a string into tokens based on some delimiter. Since it isn’t provided by the standard, we can write such a function ourselves. Create a file called StringUtil.hpp in your include directory that you created above and put the below code in it.

#pragma once

#include <string>
#include <vector>

/*
 * Splits the given string into tokens by using the provided delimiter
 * The delimiter is a space character by default.
 *
 * @return a vector of strings containing the tokens
 *
 */
std::vector<std::string> split(const std::string &s, char delim = ' ');

Let’s now implement the split() function in an implementation file called StringUtil.cpp. Place StringUtil.cpp into the source directory you created earlier.

#include "StringUtil.hpp"
#include <sstream>

std::vector<std::string> split(const std::string &s, char delim)
{
    std::vector<std::string> vec;
    std::stringstream stream(s);
    std::string token;

    while (std::getline(stream, token, delim)) {
        vec.push_back(token);
    }

    return vec;
}

Now we create our driver file that will test our implementation of split(). Create a file called main.cpp in a separate directory.

#include "StringUtil.hpp"
#include <iostream>

void print(const std::vector<std::string> &v)
{
    for (const auto &s : v)
        std::cout << s << " ";
}

int main()
{
    std::string sentence("I have a sentence to be split by spaces.");
    auto tokens = split(sentence);

    std::cout << "The sentence before splitting is: " << sentence << "\n";
    std::cout << "The sentence after splitting is: \n";

    print(tokens);
}

Now we have all the files we need to build our library and our executable. Go into the source directory and type the following command to create object files from our source:

g++ -std=c++14 -Wall -I/home/ta5578/include -c *.cpp

You should notice that an object file called StringUtil.o was created in the directory. This is the object file that will be put into our static library. As for the command itself, g++ is the command to invoke the compiler, -std=c++14 tells the compiler to compile the code using the C++14 standard, -Wall compiles with all warnings enabled, -I is to give additional directories for the compiler to look for include files. In my case, I created my include directory in my home directory so I told the compiler to look in ~/include so that it could find my StringUtil.hpp header file. -c tells the compiler to only create object files. Now, let’s finally create our library. Run the following command in the source directory we created earlier.

ar -cvq ~/lib/libStringUtil.a *.o

This should place a file called libStringUtil.a into the ~/lib directory. The ar command is the command that does all of the hard work associated with creating and maintaining library archives. -c silences some informational messages that are displayed to standard error whenever an archive is created. The -v option produces verbose output, and the -q option quickly appends all the specified object files into the archive. If the archive didn’t exist, it creates a new one.

IMPORTANT: make sure you prepend all of your static library file names with the lib prefix. This is standard protocol for GCC library files.

Now that we have created the static library, it is time to use it to compile and build our final binary. Go into the directory where you created main.cpp and run the following command:

g++ -std=c++14 -Wall -o ~/bin/Driver main.cpp -I/home/ta5578/include /home/ta5578/lib/libStringUtil.a

This command will compile our main.cpp file and put the produced binary called Driver into the bin directory that we created earlier.