📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

Software Development with C++: Dynamic and Static Linking

Nick8:54

Transcription

Hey everyone, I'm Nick, and this is Software Development with C++.

In this episode of the series, we're going to discuss the basics of static and dynamic linking.

It's often the case that we want to use code implemented as part of some library, and a great example of this is the C++ standard library. The way we use this code is through linking, so we need to link against this library to access the code we want to use.

There are two major forms of linking: static linking and dynamic linking. Today, we'll look at the basics of these two forms of linking and how they differ, using a very simple example.

Our simple example will be a main.cpp file, which is essentially a "Hello, World!" program. It has a main function that prints "Hello, World!" using std::cout before returning zero. We'll see how we can compile this by dynamically linking against our standard library versus compiling it with static linking against our standard library.

By default, when we compile an executable with something like g++, it automatically dynamically links against our standard library. For example, if I run `g++ main.cpp -o main_dynamic`, it will automatically link against our standard library.

We can change this and tell our compiler that we want to statically link against our standard library. In the case of GCC, that would be `libstdc++.so`. We can do this with a simple flag. Let's change our output executable to `main_static` and pass the flag `-static-libstdc++`.

Now, we've created two different executables. We can run both, and they will give us the same result: both will print "Hello, World!" But the difference lies underneath the hood.

First, we can look at the dependencies of these two executables, specifically their dependencies on shared libraries. One way to inspect that on Linux machines is through the tool `ldd`.

If we run `ldd main_dynamic`, we get a list of dependencies, and you can see that we depend on `libstdc++.so`, along with its location. This means that when we run the executable `main_dynamic`, our dynamic linker needs to find this shared library. We're relying on the implementation contained within `libstdc++.so`.

In contrast, if we run `ldd main_static`, we see that we no longer depend on `libstdc++.so`. We have a few other shared libraries that are not statically linked, but `libstdc++.so` is absent from the list of dependencies.

What does this mean? It means that since we statically linked `main_static`, we grabbed the information we needed from the standard library and built it into our executable. Therefore, we no longer need access to `libstdc++.so` at runtime.

One major difference between static and dynamic linking is that with dynamic linking, we need to load the code from our shared library at runtime. But with static linking, that code is already built into our executable.

Another way to see this difference is by inspecting the size of our executables. If we run `ls -la`, we can see that our statically linked executable is significantly larger than our dynamically linked one. This is because the necessary components from `libstdc++` are built into `main_static`, while `main_dynamic` still relies on `libstdc++.so` at runtime.

We can also disassemble these two executables to see more about what's happening under the hood. Let's use `objdump` to disassemble our executables. We'll run `objdump -dC main_dynamic > out_dynamic.s` and do the same for `main_static`.

Now, we can open these two disassembled files.

Looking at the main function in both, they appear fairly similar, with just a few minor differences. We still have some load effective address for `std::cout`. However, in our dynamically linked version, we see `std::cout` at `glibcxx 3.4`, while in our statically linked version, we just have `std::cout`.

Both have a call to the operator `<<` for printing our "Hello, World!" string. If we follow the address for the call in our dynamically linked example, we see something different.

In the dynamically linked example, we have a stub function with a PLT (Procedure Linkage Table) entry. This means the actual implementation of the function is not contained within this file. Instead, we use the PLT and the Global Offset Table to jump to the correct function at runtime after our dynamic linker has loaded the information from the shared library.

In contrast, in our statically linked example, we see the full implementation of the function. We grabbed the necessary information from `libstdc++.so`, and it's built into our executable.

These are some of the differences between static and dynamic linking. The core difference is where the code we want to access from the library resides concerning our final executable.

In dynamic linking, the code from the library still exists as part of a shared library, and we load it at runtime when needed. In static linking, we build that code into our executable, breaking the dependency.

As you can imagine, there are trade-offs here. There are trade-offs related to the time it takes to load things from shared libraries in dynamic linking, as well as trade-offs related to the size of executables in static linking.

That's the basics of static and dynamic linking. We'll discuss this more in later videos, especially when we get into building our own shared libraries and linking against those.

As always, you can find this and my other examples at github.com/coffeebeforearch.

That's going to do it for today. As always, I'm Nick, and I hope you have a nice day!