Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

C++  Module 15 – Build systems, testing and tooling

Static and shared libraries in C++

Build one C++ library as a static archive and as a shared library with CMake, compare what each puts in the program, and see what the loader needs.

  • Intermediate
  • 20 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Static or shared: copied in once, or loaded at every start · One CMakeLists.txt builds both, and the shared one exports more · Check yourself. With a pass: 3 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Build a static and a shared library from one CMakeLists.txt
  • Limit which symbols a shared library exports
  • Fix library-not-loaded errors after a program is installed or moved
  • Recognise library changes that break programs built against an older version

Before you start

The three statistics functions of this lesson’s mathx library can reach a program in two ways: copied into it when it is linked, which made the program 14,928 bytes larger, or loaded from a file of their own every time it starts. Those are the two kinds of library C++ has. A static library (libmathx.a on Linux and macOS, mathx.lib on Windows) is an archive of object files: when a program is linked, the linker: The last stage of a build: it joins the object files of a program and the libraries it uses into one executable, matching every symbol a file uses with exactly one definition. More in “How C++ compiles: preprocess, compile and link” copies the code it needs into the executable, and the library file is not needed after that. A shared library (libmathx.so on Linux, libmathx.dylib on macOS, mathx.dll on Windows) stays a separate file: the program records only its name, and the dynamic loader: The part of the operating system that loads the shared libraries a program records, every time the program starts: dyld on macOS, ld.so on Linux. finds and loads it every time the program starts.

Static linking gives one self-contained program. Shared linking lets many programs use one copy and lets you fix the library without relinking them, at the price of shipping it, letting the loader find it and keeping it compatible.

Static or shared: copied in once, or loaded at every start

Every row of this table follows from when the library’s code joins the program:

Static library Shared library
File libname.a (Linux, macOS), name.lib (Windows) libname.so (Linux), libname.dylib (macOS), name.dll plus an import name.lib (Windows)
Joins the program Once, when it is linked: the linker copies the object files it needs Every time the program starts: the dynamic loader loads the file
What you ship The program only The program and the library, somewhere the loader looks
A bug fix in the library Relink every program Replace one file, if the new version stays compatible
What users see Only what the program calls Every symbol the library exports
Typical use Command-line tools, contest code, small libraries inside one project System libraries, plugins, libraries that many programs share or that update on their own schedule

The diagram follows both routes for the report program of the next section: the linker copying code out of the archive once, and the loader finding the shared file again at every start.

Two panels: a static library copied into the program by the linker, and a shared library found and loaded by the dynamic loader at start.Static: joined at link timeShared: joined at every startlibmathx.aan archive of object filesLinkerreportholds its own copyof the mathx codereportrecords @rpath/libmathx.dylibDynamic loader(dyld, ld.so)libmathx.dylibone copy, sharedby every programcopies the code it needsneedssearches the rpath,then loads

A static library joins the program when it is linked; a shared library each time it starts

Text description of the diagram

The top panel shows a static library. libmathx.a, an archive of object files, goes to the linker, which copies the code the program needs into report. The finished program holds its own copy of the mathx code and needs nothing else at run time.

The bottom panel shows a shared library built from the same source code. The program report records only a reference, @rpath/libmathx.dylib. Every time the program starts, the dynamic loader (dyld on macOS, ld.so on Linux) searches the program's run-path folders for that file and loads libmathx.dylib, one copy of which can serve every program that uses it.

Key idea

A static library’s code is copied into each program when it is linked, so nothing else ships. A shared library is found and loaded at every start, so it must be shipped, found by the loader and kept compatible.

Checkpoint

Check: When library code joins the program

  1. Question 1 of 2 What do you ship with a program that uses the shared libmathx.dylib?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The program and the library, somewhere the loader looks

    A static library joins the program once, when it is linked. A shared library joins it every time it starts, so it ships with the program, somewhere the loader looks.

  2. Question 2 of 2 A security fix lands in mathx. What must happen to the programs that were linked with the static libmathx.a?
    Show the answer: Question 2 of 2

    Answer: Each program is linked again with the fixed library and shipped again

    The comparison table says it: a bug fix in a static library means relinking every program; in a shared library it means replacing one file, as long as the new version stays compatible.

One CMakeLists.txt builds both, and the shared one exports more

Static and shared libraries in C++ are built from the same source code, and CMake switches between them with one setting. The mathx library computes a mean, a median and a standard deviation, and report prints them for a week of monsoon rainfall. add_library names no type here, so the BUILD_SHARED_LIBS variable decides: OFF, the default, gives a static library, and ON a shared one.

mathx/CMakeLists.txt Text · mathx/CMakeLists.txt
cmake_minimum_required(VERSION 3.28...4.4)
project(mathx VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)

# With no STATIC or SHARED here, BUILD_SHARED_LIBS decides: OFF (the default) builds libmathx.a, ON libmathx.dylib.
add_library(mathx src/stats.cpp)
target_include_directories(mathx PUBLIC include)
target_compile_features(mathx PUBLIC cxx_std_23)

add_executable(report app/main.cpp)
target_link_libraries(report PRIVATE mathx)
The library and the program

C++ header · mathx/include/mathx/stats.hpp

#pragma once

#include <span>

namespace mathx {

// The arithmetic mean of the values (0 for none).
double mean(std::span<const double> values);

// The middle value, or the mean of the two middle values (0 for none).
double median(std::span<const double> values);

// The population standard deviation (0 for none).
double stddev(std::span<const double> values);

}  // namespace mathx

C++ (stats.cpp) · mathx/src/stats.cpp

#include "mathx/stats.hpp"

#include <algorithm>
#include <cmath>
#include <numeric>
#include <vector>

namespace mathx {

namespace {

// Used only inside this file: not part of the library's interface.
double sum_of_squares(std::span<const double> values, double centre) {
    double total = 0.0;
    for (double v : values) total += (v - centre) * (v - centre);
    return total;
}

}  // namespace

double mean(std::span<const double> values) {
    if (values.empty()) return 0.0;
    return std::accumulate(values.begin(), values.end(), 0.0) / static_cast<double>(values.size());
}

double median(std::span<const double> values) {
    if (values.empty()) return 0.0;
    std::vector<double> sorted(values.begin(), values.end());
    std::ranges::sort(sorted);
    const auto n = sorted.size();
    return n % 2 == 1 ? sorted[n / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2.0;
}

double stddev(std::span<const double> values) {
    if (values.empty()) return 0.0;
    return std::sqrt(sum_of_squares(values, mean(values)) / static_cast<double>(values.size()));
}

}  // namespace mathx

C++ (main.cpp) · mathx/app/main.cpp

#include <array>
#include <print>

#include "mathx/stats.hpp"

int main() {
    // Rainfall in millimetres on seven days of a monsoon week.
    constexpr std::array<double, 7> rain{12.0, 48.5, 3.0, 0.0, 61.0, 27.5, 9.0};
    std::println("mean   {:6.2f} mm", mathx::mean(rain));
    std::println("median {:6.2f} mm", mathx::median(rain));
    std::println("stddev {:6.2f} mm", mathx::stddev(rain));
}
The same library, static and shared Python · both_session.py
from session import run

# One CMakeLists.txt, two build folders: BUILD_SHARED_LIBS chooses the kind of library.
run("""
cd mathx
cmake -S . -B build-static -G Ninja -DBUILD_SHARED_LIBS=OFF
cmake -S . -B build-shared -G Ninja -DBUILD_SHARED_LIBS=ON
""", keep=r"^-- Build files")
run("""
cmake --build build-static
cmake --build build-shared
./build-static/report
./build-shared/report
wc -c build-static/libmathx.a build-static/report build-shared/libmathx.dylib build-shared/report
nm -gU build-static/report | c++filt | grep mathx
nm -gU build-shared/libmathx.dylib | c++filt
otool -L build-shared/report
""")

Output

$ cd mathx
$ cmake -S . -B build-static -G Ninja -DBUILD_SHARED_LIBS=OFF
-- Build files have been written to: build-static
[8 lines not shown]
$ cmake -S . -B build-shared -G Ninja -DBUILD_SHARED_LIBS=ON
-- Build files have been written to: build-shared
[8 lines not shown]
$ cmake --build build-static
[1/4] Building CXX object CMakeFiles/mathx.dir/src/stats.cpp.o
[2/4] Linking CXX static library libmathx.a
[3/4] Building CXX object CMakeFiles/report.dir/app/main.cpp.o
[4/4] Linking CXX executable report
$ cmake --build build-shared
[1/4] Building CXX object CMakeFiles/mathx.dir/src/stats.cpp.o
[2/4] Linking CXX shared library libmathx.dylib
[3/4] Building CXX object CMakeFiles/report.dir/app/main.cpp.o
[4/4] Linking CXX executable report
$ ./build-static/report
mean    23.00 mm
median  12.00 mm
stddev  21.91 mm
$ ./build-shared/report
mean    23.00 mm
median  12.00 mm
stddev  21.91 mm
$ wc -c build-static/libmathx.a build-static/report build-shared/libmathx.dylib build-shared/report
   44448 build-static/libmathx.a
  332440 build-static/report
   50384 build-shared/libmathx.dylib
  317512 build-shared/report
  744784 total
$ nm -gU build-static/report | c++filt | grep mathx
000000010001bea4 T mathx::mean(std::__1::span<double const, 18446744073709551615ul>)
000000010001c064 T mathx::median(std::__1::span<double const, 18446744073709551615ul>)
000000010001c2f0 T mathx::stddev(std::__1::span<double const, 18446744073709551615ul>)
$ nm -gU build-shared/libmathx.dylib | c++filt
00000000000010c8 T mathx::mean(std::__1::span<double const, 18446744073709551615ul>)
0000000000001288 T mathx::median(std::__1::span<double const, 18446744073709551615ul>)
0000000000001514 T mathx::stddev(std::__1::span<double const, 18446744073709551615ul>)
0000000000001ee4 T std::__1::allocation_result<double*, unsigned long> std::__1::allocator_traits<std::__1::allocator<double>>::allocate_at_least[abi:nqe220106]<std::__1::allocator<double>>(std::__1::allocator<double>&, unsigned long)
0000000000002a90 T void std::__1::allocator_traits<std::__1::allocator<double>>::destroy[abi:nqe220106]<double, 0>(std::__1::allocator<double>&, double*)
0000000000001d14 T unsigned long std::__1::allocator_traits<std::__1::allocator<double>>::max_size[abi:nqe220106]<std::__1::allocator<double>, 0>(std::__1::allocator<double> const&)
000000000000278c T void std::__1::allocator_traits<std::__1::allocator<double>>::construct[abi:nqe220106]<double, double const&, 0>(std::__1::allocator<double>&, double*, double const&)
000000000000364a S std::__1::ranges::__cpo::end
000000000000364b S std::__1::ranges::__cpo::next
0000000000003648 S std::__1::ranges::__cpo::sort
0000000000003649 S std::__1::ranges::__cpo::begin
000000000000364c S std::__1::ranges::__cpo::advance
0000000000001818 T void std::__1::vector<double, std::__1::allocator<double>>::__init_with_size[abi:nqe220106]<std::__1::__wrap_iter<double const*>, std::__1::__wrap_iter<double const*>>(std::__1::__wrap_iter<double const*>, std::__1::__wrap_iter<double const*>, unsigned long)
0000000000001a58 T void std::__1::vector<double, std::__1::allocator<double>>::__construct_at_end<std::__1::__wrap_iter<double const*>, std::__1::__wrap_iter<double const*>>(std::__1::__wrap_iter<double const*>, std::__1::__wrap_iter<double const*>, unsigned long)
0000000000003374 T double* std::__1::vector<double, std::__1::allocator<double>>::__add_alignment_assumption[abi:nqe220106]<double*, 0>(double*)
$ otool -L build-shared/report
build-shared/report:
	@rpath/libmathx.dylib (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 2200.27.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1359.0.0)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 both_session.py

Step through the two builds side by side, with the sizes and the symbols the session printed:

Interactive · step by step The same library built static and shared, measured Step through both builds of the session. The left column is the folder build-static, the right one build-shared.
The same library built static and shared, measured, step 6 of 6. Showing libmathx.a (44,448 bytes), libmathx.dylib (exports 15 symbols), report (static) (332,440 bytes), report (shared) (317,512 bytes), Static run (mean 23.00 mm), Shared run (mean 23.00 mm). In focus: libmathx.dylib.copied inloaded at startlibmathx.a44,448 byteslibmathx.dylibexports 15 symbolsreport (static)332,440 bytesreport (shared)317,512 bytesStatic runmean 23.00 mmShared runmean 23.00 mm

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Two folders. The session configured one CMakeLists.txt twice: build-static with BUILD_SHARED_LIBS=OFF and build-shared with ON. Step 2 of each build linked a different kind of library.
  2. Two library files. The static build wrote the archive libmathx.a, 44,448 bytes. The shared build wrote libmathx.dylib, 50,384 bytes.
  3. Copied in. The static report holds the three mathx functions itself: nm lists mean, median and stddev at addresses of the program, marked T for code. It is 332,440 bytes.
  4. Recorded, not copied. The shared report is 317,512 bytes, 14,928 fewer, and holds none of them. otool -L shows what it records instead, @rpath/libmathx.dylib, a name the loader resolves when the program starts.
  5. The same three lines. Both programs printed a mean of 23.00 mm, a median of 12.00 mm and a standard deviation of 21.91 mm. Each one is over 300 KB because it holds its own copy of the formatting code behind std::println.
  6. What the library offers. The shared library exported 15 symbols, not 3: the functions of its header and 12 pieces of the standard library that its code instantiated, such as std::vector's allocation.

Both programs printed the same three lines. The static report carries the mathx functions inside it: nm lists them at addresses of the program itself, marked T for code. The shared report is 14,928 bytes smaller and contains none of them; otool -L shows that it needs @rpath/libmathx.dylib instead, a name the loader resolves when the program starts. Both programs weigh over 300 KB because each one holds its own copy of the formatting code behind std::println.

The shared library exported 15 symbol: A name in an object file's table, such as a function's mangled name. nm marks a symbol the file defines in its code with T, and one it uses but leaves to another file with U. More in “How C++ compiles: preprocess, compile and link”, not 3. The other 12 are pieces of the standard library, from std::vector’s allocation to std::ranges::sort, that the library’s code instantiated and that became part of what any program can link against. Static and shared libraries in C++ differ most in this kind of detail, which a static library never exposes to anyone.

Checkpoint

Check: One CMakeLists.txt, both kinds of library

  1. Question 1 of 2 The static report was 332,440 bytes and the shared one 317,512 bytes. How many bytes larger was the static program?

    Type a number.

    A hint

    Subtract the two sizes that wc -c printed.

    Show the answer: Question 1 of 2

    Answer: 14928 bytes

    332,440 − 317,512 = 14,928 bytes: the code of the three mathx functions, and what they need, copied into the static program. The shared program records the library's name instead.

  2. Question 2 of 2 What did otool -L show that the shared report records about the mathx library?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: A name, @rpath/libmathx.dylib, that the loader resolves at start

    A shared library stays a separate file, and the program records only its name. otool -L listed @rpath/libmathx.dylib, followed by the system's libc++ and libSystem.

What decides whether a shared library keeps working

Shared libraries bring a few names that appear in build files and in error messages:

  • symbol visibility: Whether a symbol of a shared library is exported, so that other programs can link against it. The flag -fvisibility=hidden makes every symbol hidden unless the code marks it otherwise. decides which functions a shared library exports; -fvisibility=hidden hides all of them by default.
  • Export macros such as MATHX_EXPORT mark the declarations that form the library’s interface.
  • The install name and the rpath: A list of folders recorded in a program or a library, where the dynamic loader looks for the shared libraries it needs, such as the folders that @rpath in an install name may stand for. are what the program records: @rpath/libmathx.dylib, and a list of folders where @rpath may point.
  • @loader_path and $ORIGIN start an rpath at the program’s own folder, so an installed folder can move.
  • DYLD_LIBRARY_PATH and LD_LIBRARY_PATH add folders to the loader’s search through the environment.
  • ABI (application binary interface): What compiled code relies on: the sizes and layouts of types, the symbols and the calling conventions. A new version of a library keeps its ABI when programs built against the old version still work with it. compatibility means a new version keeps the sizes, layouts and symbols that programs were compiled against.
  • SOVERSION puts the ABI version into the file name, such as libmathx.so.1, so two versions can be installed.

Each name decides something at one moment in a library’s life: when it is built, when a program starts, or when a new version replaces an old one.

Three moments in the life of a shared library, built, started and updated, with the names of this lesson that decide each one.When the library is builtAt every start of a programWhen a new version is installedSymbol visibilitywhich functions are exportedExport macroswhich declarations form the interfaceInstall name and rpathwhat the program records@loader_path and $ORIGINan rpath that starts at the programDYLD_LIBRARY_PATH, LD_LIBRARY_PATHfolders added by the environmentABI compatibilitysizes, layouts and symbols keptSOVERSIONthe ABI version in the file namethenlater

When each name decides whether a shared library keeps working

Text description of the diagram

The diagram is a column of three groups joined by arrows, one for each moment in the life of a shared library.

  1. When the library is built: symbol visibility decides which functions are exported, and export macros mark the declarations that form the interface.
  2. At every start of a program: the install name and the rpath are what the program records; @loader_path and $ORIGIN start an rpath at the program's own folder; DYLD_LIBRARY_PATH and LD_LIBRARY_PATH add folders through the environment.
  3. When a new version is installed: ABI compatibility means the sizes, layouts and symbols that programs were compiled against are kept, and SOVERSION puts the ABI version into the file name.
Hex Viewer & File Inspector Open libmathx.a, libmathx.dylib or report to see the first bytes that tell an archive from a Mach-O program, and which CPU it was built for.

Check yourself

Nine questions about the two kinds of library and the names above, starting with the idea you are least sure of. Each answer says at once why it is right or what it leaves out.

Practice that adapts to you

Practise this lesson

9 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 9 When does the code of a static library become part of a program?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: When the program is linked; the library file is not needed after that

    The linker copies the object files the program needs out of the archive. That is why the lesson's static report held the three mathx functions itself, while the shared report only recorded @rpath/libmathx.dylib for the loader to find at start.

  2. Question 2 of 9 A bug in the shared libmathx.dylib is fixed without changing any declaration in its header. What must happen to the programs that use it?

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: Nothing: install the fixed file, and each program loads it at its next start

    This is the main reason to ship a shared library: one file is replaced and every program picks up the fix, as long as the new version stays compatible with what the programs were compiled against.

  3. Question 3 of 9 You ship one command-line tool to users who should install nothing else. How should it get the code of your own helper library?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: Link the helper library statically, so the tool carries its code

    The comparison table lists command-line tools and small libraries inside one project as the typical use of static libraries: one self-contained program. Shared libraries pay off when many programs share one copy or when the library updates on its own schedule.

  4. Question 4 of 9 add_library(mathx src/stats.cpp) names no STATIC or SHARED. What decides the kind of library?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: The BUILD_SHARED_LIBS variable, OFF unless you set it

    Without a type, add_library follows BUILD_SHARED_LIBS. The lesson configured one folder with -DBUILD_SHARED_LIBS=OFF and one with ON from the same CMakeLists.txt, and got libmathx.a and libmathx.dylib.

  5. Question 5 of 9 The shared libmathx.dylib exported 15 symbols, though its header declares 3 functions. What were the other 12?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: Standard-library template code, such as std::vector's allocation, that the library instantiated

    The 12 extra symbols were pieces of the standard library, from std::vector's allocation to std::ranges::sort, that the library's code instantiated. They became part of what any program can link against.

  6. Question 6 of 9 Both report programs weigh over 300 KB, though mathx is three small functions. Why?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: Each program holds its own copy of the formatting code behind std::println

    The difference between the two programs, 14,928 bytes, is the mathx code. The 300 KB that both share is the formatting code of std::println, which each program carries.

  7. Question 7 of 9 Which setting puts the ABI version into the file name of a shared library, so two versions can be installed?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: SOVERSION

    SOVERSION puts the ABI version into the file name, such as libmathx.so.1, so that two versions of the library can be installed side by side.

  8. Question 8 of 9 Which compiler flag hides every symbol of a shared library unless the code marks it otherwise?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: -fvisibility=hidden

    -fvisibility=hidden hides all of a shared library's symbols by default, and export macros such as MATHX_EXPORT mark the declarations that form its interface.

  9. Question 9 of 9 Programs built against version 1 of a shared library print wrong numbers after version 2 is installed. Which of the lesson's names is the problem about?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: ABI compatibility

    ABI compatibility means a new version keeps the sizes, layouts and symbols that programs were compiled against. When it does not, programs can run and print wrong values without any error.

Exporting only the interface: hidden visibility and export macros, measured Pro: opens with a Pro, Premium or Ultimate pass

A shared library that exports everything makes its internal code part of what other programs may depend on. Hiding everything by default and marking the public functions turns the export list into a deliberate interface.

In this part:

  • Hidden by default, exported on purpose
  • A missing export macro, reproduced
  • What each platform exports unless told otherwise

4 runnable examples (C++ header, Python, Text) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

When the program cannot find its library: rpath failures reproduced and fixed Pro: opens with a Pro, Premium or Ultimate pass

A shared library has to be found again every time its program starts, and each system searches differently. The failures below appear as soon as a program leaves the folder it was built in.

In this part:

  • Where each loader looks
  • Built and working, installed and broken
  • Why DYLD_LIBRARY_PATH seems to stop working
  • Reading a loader failure in three steps

1 diagram · 3 runnable examples (CMAKE, Python) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

ABI breaks: an old program against a changed library, reproduced Pro: opens with a Pro, Premium or Ultimate pass

Programs keep running against whatever version of a shared library is installed, using the sizes and layouts they were compiled with. When a new version changes those, nothing reports an error and the results are simply wrong.

In this part:

  • One new member, two wrong numbers
  • Changes that break the ABI, and changes that do not
  • Version numbers that keep old programs working
  • Exercise: make a library update safe

1 diagram · 4 runnable examples (C++, C++ header (summary.hpp), Python, Text) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Interview questions

  1. Fresher · Warm-up

    What is the difference between a static and a shared library in C++?

    A static library (.a or .lib) is an archive of object files. When a program is linked, the linker copies the code the program needs into the executable, so the program runs on its own and the library is not needed afterwards. A shared library (.so, .dylib or .dll) stays a separate file: the program records its name, and the dynamic loader finds and loads it every time the program starts. A shared library can be used by many programs at once and replaced without relinking them, but it has to be shipped with the program, found by the loader and kept compatible with the programs built against it. In CMake, add_library(name STATIC …) or SHARED chooses, and without a type the BUILD_SHARED_LIBS variable decides.

  2. Mid to senior

    Your macOS program runs from the build folder, but the zip you send to testers fails at start with "Library not loaded: @rpath/libgeo.dylib" and "Reason: no LC_RPATH's found". Explain what happened, and fix the build for macOS and Linux without asking testers to set any variable.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    A bug-fix release of your shared library adds one field to a public struct. Programs built against the previous release, which nobody recompiled, now print wrong values but do not crash. Explain the mechanism, and how you would ship the change safely.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    Your team ships a C++ SDK that other companies link into their apps on Windows, macOS and Linux. Should it be a static or a shared library? Defend the choice, and say what you would require from the users of the SDK either way.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A static library is copied into the program at link time; a shared library is found and loaded every time the program starts.
  • One CMakeLists.txt builds both: add_library without a type follows BUILD_SHARED_LIBS.
  • The static report carried the library’s code and was 14,928 bytes larger; the shared one recorded @rpath/libmathx.dylib instead.
  • A shared library exports more than its header suggests unless its visibility is controlled: 15 symbols here, 3 of them the library’s own functions.

Terms in this lesson

ABI (application binary interface)
What compiled code relies on: the sizes and layouts of types, the symbols and the calling conventions. A new version of a library keeps its ABI when programs built against the old version still work with it.
dynamic loader
The part of the operating system that loads the shared libraries a program records, every time the program starts: dyld on macOS, ld.so on Linux.
rpath
A list of folders recorded in a program or a library, where the dynamic loader looks for the shared libraries it needs, such as the folders that @rpath in an install name may stand for.
shared library
A library that stays a file of its own, such as libmathx.so, libmathx.dylib or mathx.dll. A program records its name, and the dynamic loader finds and loads it every time the program starts.
static library
An archive of object files, such as libmathx.a or mathx.lib. The linker copies the code a program needs out of it, so the program does not need the library file once it is linked.
symbol visibility
Whether a symbol of a shared library is exported, so that other programs can link against it. The flag -fvisibility=hidden makes every symbol hidden unless the code marks it otherwise.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.