CreateLibrariesAndExecutables
This page shows how to create a minimal C/C++ software library, first in a static variation, then in a shared variation, and then how to create a C/C++ executable program that uses that static or shared library.
The examples make use of the Clang compiler. They were tested on macOS, but should hopefully work with minimal changes on Linux as well. I have no idea about how Clang works on Windows.
The page Compiling Linking Notes on this wiki has useful information about shared/static libraries mechanisms on different platforms. The page is written in German. Note to self: I should integrate some if not all of the information on that page to here.
References
- A good primer on how to create shared libraries on Linux: https://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
- A two-article series with some basics on object files and symbols (part 1) and then a tutorial on creating shared libraries on macOS (part 2).
Glossary
- Dynamic link library
- Another name used on the Windows platform when referring to a ➔ Shared library. The term is commonly abbreviated to "DLL".
- Executable program
- A piece of software that can be executed standalone.
- Library
- A piece of software that is reusable but that cannot be executed standalone. Libraries can be reused by executable programs, or by other libraries. There are two types of libraries: ➔ Static libraries and ➔ Shared libraries.
- Shared library
- A software library that contains reusable compiled program code. The difference to a ➔ Static library is that when the shared library is consumed at build time, its content is not duplicated by the consumer. The shared library is therefore required at runtime, because the consumer does not contain a copy of the library code.
- Static library
- A software library that contains reusable compiled program code. When the static library is consumed at build time, its content is duplicated and the copy is embedded into the consumer. The static library is not required at runtime, because the consumer already contains a copy of the code.
Project structure
This folder structure is the basis for the remainder of this page. It contains folders for two projects:
- A library project. This can be used to produce both a static and a shared/dynamic library.
- An executable project. This is used to produce an executable program that uses the library.
base folder +-- library project folder | +-- include | | +-- HelloWorld.h | +-- src | | +-- HelloWorld.cpp | +-- script | | +-- build-shared-library.sh | | +-- build-static-library.sh | +-- build | | +-- HelloWorld.o | +-- lib | +-- libhelloworld.a / libhelloworld.dylib +-- executable project folder +-- src | +-- main.cpp +-- script | +-- build-executable-with-shared-library.sh | +-- build-executable-with-static-library.sh +-- build | +-- main.o +-- bin +-- sayhelloworld
Discussion of the library project structure:
- The library project's
include
folder contains the header files with the public API of the library. Keeping these separate from the rest of the library code is advisable so that packaging the library becomes more easy. - The library project's
src
folder contains the source code of the library. If you have header files that are internal to your library, you can place them in here as well. - The library project's
script
folder contains the scripts that drive the software build. Keeping scripts separate from the source code is good practice. - The library project's
build
folder contains intermediate products of the software build. Typically these are object files with an .o extension (.obj on Windows). - The library project's
lib
folder contains the end products of the software build, i.e. the actual static or shared library. Static and shared libraries use different file extensions, also different platforms use different file extensions. Keeping the end products separate from the intermediate products is advisable so that packaging the library becomes more easy.
Discussion of the executable project structure:
- The executable project's
src
,script
andbuild
folders have the same purpose as the library project's folders of the same name. - The executable project does not have an
include
folder because it does not need to separate public from internal API. - The executable project's
bin
folder contains the end products of the software build, i.e. the actual executable program. It has the same purpose as the library project'slib
folder.
How to build and consume a static library
Library source code
HelloWorld.h
#ifndef HELLOWORLD_H #define HELLOWORLD_H namespace HelloWorld { void SayHelloWorld(); } #endif
HelloWorld.cpp
// Platform includes #include <iostream> // Project includes #include <HelloWorld.h> namespace HelloWorld { void SayHelloWorld() { std::cout << "Hello World" << std::endl; } }
Library build script
build-static-library.sh
#!/usr/bin/env bash INCLUDE_FOLDER="include" SOURCE_FOLDER="src" INTERMEDIATE_PRODUCTS_FOLDER="build" END_PRODUCTS_FOLDER="lib" test ! -d "$INTERMEDIATE_PRODUCTS_FOLDER" && mkdir "$INTERMEDIATE_PRODUCTS_FOLDER" test ! -d "$END_PRODUCTS_FOLDER" && mkdir "$END_PRODUCTS_FOLDER" clang \ -c \ -I "$INCLUDE_FOLDER" \ -o "$INTERMEDIATE_PRODUCTS_FOLDER/HelloWorld.o" \ "$SOURCE_FOLDER/HelloWorld.cpp" ar \ -r \ -c \ "$END_PRODUCTS_FOLDER/libhelloworld.a" \ "$INTERMEDIATE_PRODUCTS_FOLDER/HelloWorld.o"
As you can see, the build consists of two distinct steps:
- A compile step
- A step that creates the static library
Discussion of the compilation step:
- The
-c
option tells the compiler to perform compilation. - The
-I
option tells the compiler where to look for include files. Without this option compilation would fail on this line#include <HelloWorld.h>
inHelloWorld.cpp
, because the compiler would be unable to find theHelloWorld.h
file. - The
-o
option tells the compiler where to put the intermediate object file, and how to name it. Without this option the compiler would store the object file in the current working directory, and the object file would have the same base name as the source file. - The last argument is the source code file to compiler.
- It is possible to specify several source code files, but then the
-o
option, which specifies a single output file, would have to be omitted.
- It is possible to specify several source code files, but then the
- The source code file's extension provides a hint to the compiler what programming language it can expect in the file. In our case the
.cpp
file extension tells the compiler that this is a C++ file, which causes it to operate in C++ mode. One effect of this mode is that the compiler automatically adds include paths in the background that tell it where to find the header files of the C++ Standard Library. Because of this the line#include <iostream>
does not cause a compiler error.- If the source code file were named
HelloWorld.c
, then the compiler would assume that this is a C file. The C++ Standard Library headers would not be found and there would be a compiler error. - One way to "fix" this would be to run the compiler with the
clang++
command. This causes the compiler to permanently run in C++ mode. This is not recommended. - The
-x <language>
option is the correct way to explicitly tell the compiler what programming language to expect, and not to infer the programming language from the file extension. Unfortunately theclang
man page does not document what the possible values are for-x
. Experimentally determined to work:-x c
and-x c++
.
- If the source code file were named
Discussion of the step that creates the static library:
- The
ar
command is used to create a library archive. On UNIX-like platforms a library archive is effectively the same as a static library. - An archive file is basically a bunch of intermediate object files packaged together into a single file for distribution. There may be some other fancy stuff in an archive (e.g. a symbol table) but this doesn't concern us here.
- The
-r
option is required because it tells the archive utility in what mode to operate.-r
means that the utility 1) should create the archive file it it does not exist yet, 2) should replace the specified object file if it is already in the archive file, and 3) should add the object file if it isn't in the archive file yet. - As mentioned, the archive utility creates the archive file if it does not exist yet. Normally the utility would print a message to let you know about this, but the
-c
option suppresses that message. This option can be safely omitted. - The first command line argument after the options is the archive file name. All subsequent command line arguments enumerate the object files that are to be stuffed into the archive file.
- The archive file name should have a "lib" prefix. This is a naming convention on UNIX-like platforms. The convention becomes important later on when the consuming executable is built. See the section further down that discusses the executable build script.
Executable source code
main.cpp
// Library includes #include <HelloWorld.h> int main(int argc, char** argv) { HelloWorld::SayHelloWorld(); return 0; }
Executable build script
build-executable-with-static-library.sh
#!/usr/bin/env bash LIBRARY_INCLUDE_FOLDER="../library/include" LIBRARY_LIB_FOLDER="../library/lib" SOURCE_FOLDER="src" INTERMEDIATE_PRODUCTS_FOLDER="build" END_PRODUCTS_FOLDER="bin" test ! -d "$INTERMEDIATE_PRODUCTS_FOLDER" && mkdir "$INTERMEDIATE_PRODUCTS_FOLDER" test ! -d "$END_PRODUCTS_FOLDER" && mkdir "$END_PRODUCTS_FOLDER" clang \ -c \ -I "$LIBRARY_INCLUDE_FOLDER" \ -o "$INTERMEDIATE_PRODUCTS_FOLDER/main.o" \ "$SOURCE_FOLDER/main.cpp" clang \ -l helloworld \ -L "$LIBRARY_LIB_FOLDER" \ -l c++ \ -o "$END_PRODUCTS_FOLDER/sayhelloworld-staticlibrary" \ "$INTERMEDIATE_PRODUCTS_FOLDER/main.o"
As you can see, the build consists of two distinct steps:
- A compile step
- A step that creates the executable program. This is also called the linking step.
Note: The two steps can be condensed into a single step. The next section Alternatives for linking the executable program has an example of that. In this we treat and discuss the two steps separately for educational purposes.
Discussion of the compilation step:
- This is exactly the same as the compilation step during the build of the static library. For a detailed discussion of the individual flags see that section.
- The only thing notable here is that we specify an include directory that is outside the scope of the executable project, to find the public API header files of the library. If your executable were to consume a third-party library, the include directory could point to a system directory (if the library were distributed with the system) or to a third-party software repository that you maintain.
Discussion of the step that creates the executable program (aka linking step):
- Unlike when we created the static library, we don't need a separate program to create the executable program: The
clang
command can not only perform compilation steps, it can also perform the linking step.- This is why the
clang
command is sometimes called a "front-end", because it acts as a facade that runs the actual tools in the background (e.g. pre-processor, compiler, linker).
- This is why the
- The
-l helloworld
option tells the linker to add the library "helloworld" to the executable program. Because it's a static library, the linker adds a copy of the code from the library to the executable program. Only those symbols that are actually used in the executable program's source code are copied. Note how we didn't specify the actual file name of the static library!- We don't specify the
.a
file extension. The linker automatically searches for libraries with different file extensions, which enables it to automatically select the right library type for you. If both a static and a shared library with the same name are present, the linker will use the shared library. - We don't specify the
lib
prefix. The compiler automatically adds this prefix for us. This naming convention is used (and enforced) when a library is specified using the-l
option.
- We don't specify the
- The
-L
option is equivalent to the-I
option during compilation: It tells the linker where to look for library files specified with the-l
options. Without this option the linking step would fail because it would be unable to find the static library "helloworld".
- The
-l c++
option tells the linker to add the C++ Standard Library to the executable program. This is necessary because the program code in the static library makes use of the C++ Standard Library.- Important: There are two competing implementations of the C++ Standard Library:
- libstdc++, which is the implementation from the GCC project, and
- libc++, which is the implementation from the LLVM project.
- On modern versions of macOS
clang
defaults to use libc++, hence in the examples on this page we specify-l c++
. If you get linker errors pertaining to the Standard C++ Library symbols, then it is likely that on your platformclang
defaults to use libstdc++. In that case you have to change the-l
option to say-l stdc++
and the problem should disappear. You can also tellclang
to use one or the other library by specifying the-stdlib
option. Example: To use libstdc++ you say-stdlib=libstdc++
. Obviously, whenever you specify-stdlib
you must change the-l
option accordingly.
- Note that when you run the linking step with the
clang++
command this putsclang
into C++ mode and it automatically adds the appropriate-l
option (-l c++
or-l stdc++
) for you, so you don't have to explicitly specify it.
- Important: There are two competing implementations of the C++ Standard Library:
- The
-o
option tells the linker where to put the end product of the linking step (i.e. the executable program), and how to name it. Without this option the linker would store the end product in the current working directory under the default namea.out
.
- All remaining command line arguments enumerate the object files that the linker should link together. In our case it's only a single object file.
Alternatives for linking the executable program
Alternative 1: In the linking step, instead of specifying a library using the -L
and -l
options we can specify it directly as an object file. When we do it like that clang
does not enforce any naming conventions, simply because it does not know that we are linking a library - it's just an object file without any special meaning. To restate: In this scenario we have to specify the static library with its full path and its full file name, i.e. libhelloworld.a
, not just helloworld
. The command line looks like this:
clang \ -l c++ \ -o "$END_PRODUCTS_FOLDER/sayhelloworld" \ "$LIBRARY_LIB_FOLDER/libhelloworld.a" "$INTERMEDIATE_PRODUCTS_FOLDER/main.o"
Alternative 2: As mentioned multiple times in the previous section, it is possible to combine the compilation step and the linking step into a single command. The command line then looks loke this:
clang \ -I "$LIBRARY_INCLUDE_FOLDER" \ -l helloworld \ -L "$LIBRARY_LIB_FOLDER" \ -l c++ \ -o "$END_PRODUCTS_FOLDER/sayhelloworld" \ "$SOURCE_FOLDER/main.cpp"
Discussion:
- You omit the
-c
option because you don't want to compile only. - There is no intermediate object file. You specify directly the end product for the
-o
option.
Running the executable program
Just type
./bin/sayhelloworld-staticlibrary
and the program will run. This is because a copy of the code in the static library has been placed into the program executable, so it has no runtime dependencies to libhelloworld.
It does have runtime dependencies on the C++ Standard Library and the system library, though:
otool -L bin/sayhelloworld-staticlibrary bin/sayhelloworld-staticlibrary: /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.4) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
Library source code
The library source code is the same as for the static library.
Library build script
build-shared-library.sh
#!/usr/bin/env bash INCLUDE_FOLDER="include" SOURCE_FOLDER="src" INTERMEDIATE_PRODUCTS_FOLDER="build" END_PRODUCTS_FOLDER="lib" test ! -d "$INTERMEDIATE_PRODUCTS_FOLDER" && mkdir "$INTERMEDIATE_PRODUCTS_FOLDER" test ! -d "$END_PRODUCTS_FOLDER" && mkdir "$END_PRODUCTS_FOLDER" clang \ -c \ -fpic \ -I "$INCLUDE_FOLDER" \ -o "$INTERMEDIATE_PRODUCTS_FOLDER/HelloWorld.o" \ "$SOURCE_FOLDER/HelloWorld.cpp" clang \ -shared \ -l c++ \ -o "$END_PRODUCTS_FOLDER/libhelloworld.dylib" \ "$INTERMEDIATE_PRODUCTS_FOLDER/HelloWorld.o"
The general procedure with two distinct steps (compile step, create the library) is identical to how you create a static library. The main difference is that for creating the shared library you now use the clang
command instead of the ar
command. Creating the shared library has now essentially become a linking step.
Discussion of the compilation step:
- The only difference to the compilation step for static libraries is that we additionally use the
-fpic
option. This generates so-called "position independent code", or PIC for short. PIC is code that works no matter where in memory it is placed. Because several different executable programs can all use one instance of the shared library, the library cannot store things at fixed addresses, since the location of that library in memory will vary from program to program. The-fpic
option essentially converts absolute addresses to relative addresses in order to support this.
Discussion of the step that creates the shared library:
- As already mentioned, creating the shared library is a linking step. See the Executable build script section for static libraries, further up on this page, for details on how linking works.
- The only difference to creating an executable program is the additional use of the
-shared
option. This tells the linker that we want to create a shared library instead of an executable program. - A notable difference to creating a static library is that we have to add dependencies to other shared libraries already here, when we create the library. In this case we use
-l c++
to specify the dependency on the C++ Standard Library.
Executable build script
build-executable-with-shared-library.sh
#!/usr/bin/env bash LIBRARY_INCLUDE_FOLDER="../library/include" LIBRARY_LIB_FOLDER="../library/lib" SOURCE_FOLDER="src" INTERMEDIATE_PRODUCTS_FOLDER="build" END_PRODUCTS_FOLDER="bin" test ! -d "$INTERMEDIATE_PRODUCTS_FOLDER" && mkdir "$INTERMEDIATE_PRODUCTS_FOLDER" test ! -d "$END_PRODUCTS_FOLDER" && mkdir "$END_PRODUCTS_FOLDER" clang \ -c \ -I "$LIBRARY_INCLUDE_FOLDER" \ -o "$INTERMEDIATE_PRODUCTS_FOLDER/main.o" \ "$SOURCE_FOLDER/main.cpp" clang \ -l helloworld \ -L "$LIBRARY_LIB_FOLDER" \ -l c++ \ -o "$END_PRODUCTS_FOLDER/sayhelloworld-sharedlibrary" \ "$INTERMEDIATE_PRODUCTS_FOLDER/main.o"
Surprise: Except for the name of the generated binary, the script is exactly the same as the script for linking against the static library!
Running the executable program
Just typing
./bin/sayhelloworld-sharedlibrary
does not work because the executable program has a runtime dependency on the shared library libhelloworld, but the system does not know where to locate that library.
otool -L bin/sayhelloworld-sharedlibrary bin/sayhelloworld-sharedlibrary: lib/libhelloworld.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.4) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.250.1)
For testing purposes, this is easily fixed by setting the environment variable DYLD_LIBRARY_PATH
so that the system can find the missing shared library. Example:
DYLD_LIBRARY_PATH=../library/lib/ ./bin/sayhelloworld-sharedlibrary