Error: Linking a runtime binary failed¶
This error indicates that the final phases of the build pipeline, the link phases, failed.
The end result of the software development process is to produce applications and programs that can be executed by users. In the traditional compilation and linking model, which we still use to this day, multiple translation units (which can be thought of as “source files”) are combined together in a process known as linking. The result of linking is an actual executable.
Note
Linking is also used to generate executables that are used as tests. Refer: Applications and Tests.
What is “Linking”?¶
The phases of translation define the steps taken by a compiler (and linker) to map from the input human-readable source code to the code that can be executed by a machine. The first few phases are collected bundled together in a phase known as “compilation,” while the later phases are known as “linking.”
The output of the compilation phases is often generated by a compiler and then written to the filesystem. The resulting files, known as object files are then fed into another tool known as a linker.
Note
“Object files” are just one possible intermediate product. Compilers and linkers may deal with different intermediate products depending on compiler and linker options.
Symbol Reference Resolution¶
When code within a translation unit uses a function, variable, or member of a class that is declared but not defined in that translation unit, that usage is stored as an “unresolved” reference to an external symbol within the resulting object file.
Each translation unit may also contain the definitions of any number of symbols. It is possible that other translation units may contain unresolved references to the symbol that the translation unit defines.
It is the job of the linker to fill in these unresolved references when it combines translation units together.
Failure Modes¶
There are two very common types of linker errors that may be seen:
Multiple Definitions Found¶
When translation units are combined together, the symbols within them are combined together into a final binary. If two or more translation units contain the definition of a single symbol, then the linker must make a decision:
If the symbol is marked properly, then the linker can discard all except one of the definitions and choose one to keep in the final binary. For example: This is allowed if the associated symbol has been declared with the
inline
keyword, or is a symbol in any context that is “implicitlyinline
,” which includes member functions and static variables which are defined within their class’s body, and any function template.Fail
If the linker is not allowed to discard all-but-one of the multiple definitions, this is a hard-error. This can happen if multiple translation units defined the same variable or function at the same namespace.
Issue: A non-inline
function is defined in a header file¶
A likely case is that of defining a function in a header file without
marking it as inline
:
#ifndef MY_HEADER_INC
#define MY_HEADER_INC
#include <cstdio>
void say_hello() {
std::puts("Hello!\n");
}
#endif
and then that header is #include
-ed in multiple source files:
#include "hello.hpp"
// ... stuff ...
#include "hello.hpp"
// .. different stuff ...
Note
template
functions and member functions defined within the class body
are implicitly inline
, and using the inline
keyword is then
redundant.
In the above configuration, the linker will generate an error about multiple
definitions of the say_hello
function. Possibly confusingly, it will point
to a.cpp
and b.cpp
as the “definers” of say_hello
, even though it
is actually defined in the header. The issue is that no tools are currently
able to understand this structure in a way that they can clearly issue
appropriate instruction on how to fix this. There are two ways to fix this:
Add the
inline
keyword to the definition ofsay_hello
:#ifndef MY_HEADER_INC #define MY_HEADER_INC #include <cstdio> inline void say_hello() { std::puts("Hello!\n"); } #endif
This activates the rule that permits the linker to disregard the multiple definitions and choose one to keep arbitrarily.
Note
Only use
inline
in headers!Change the definition of
say_hello
to be a declaration, and move the definition to a separate source file:#ifndef MY_HEADER_INC #define MY_HEADER_INC #include <cstdio> void say_hello() { std::puts("Hello!\n"); } #endif
#include "hello.hpp" void say_hello() { std::puts("Hello!\n"); }
This will place the sole location of the
say_hello
definition withinhello.cpp
.
Issue: There are two colliding and distinct definitions¶
Suppose you have two different source files:
#include "a.hpp"
void error(string message) {
cerr << "An error occured: " << msg << '\n';
}
void a_func() {
bool had_error = first_a();
if (err) {
error(*err);
}
err = second_a();
if (err) {
error(*err);
}
}
void error(string message) {
throw runtime_error(msg);
}
void b_func() {
bool had_error = first_b();
if (had_error) {
error("The first step failed!");
}
had_error = second_b();
if (had_error) {
error("The second step failed!");
}
}
The two functions, a_func
and b_func
, despite having a similar
structure, are completely different because of the behavior of error
:
In
a.cpp
:error()
will simply log a message but let execution continue.If
first_a()
fails, execution will continue intosecond_a()
.
In
b.cpp
:error()
will throw an exception.If
first_b()
fails, execution will never reachsecond_b()
Nevertheless, the linker will produce an error that there are multiple visible
definitions of error()
, even though the translation units individually have
no ambiguity.
The issue is that both of the definitions have external linkage and must be visible to all other translation units.
It may be tempting to fix this issue in the same way that we did in the prior
example: to declare them inline
, and it will seem to have worked, but
this will not work correctly!!
Remember what the linker does in the presence of inline
on multiple
definitions between different translation units: It will pick one and
discard the others. This means that either error
function may replace the
other across translation units, and the resulting code will have wildly
different behavior.
The correct solution is to give the error
function internal linkage,
which means that its definition is not visible across translation units. This
will allow both definitions of error
to live together in the linked binary
without ambiguity. The classic way of doing this is through the usage of the
global-scope static
keyword which is present in C:
static void error(string s) {
// ...
}
C++ presents another way it can be done: via an unnamed namespace:
namespace {
void error(string s) {
// ...
}
} // close namespace
The benefit of the unnamed namespace is it can be used to mark an entire
section of declarations to be internal, and it can also be used to mark a
class definition to have internal linkage (There is no way to declare a
“static class
”).
Unresolved External Symbol / Undefined Reference¶
Another common error seen while linking is that of the unresolved external symbol (Visual C++) or undefined reference (GCC and Clang). Both have the same underlying cause, and both have the same solutions.
When a translation unit makes use of a symbol which has been declared but not defined within that translation unit, it is up to the linker to resolve that reference to another translation unit that contains the definition.
If the linker is unable to find the definition of the referenced entity, it will emit this error.
Issue: An external library is not being included in the link¶
If the unresolved reference is to an entity belonging to an external library, you may be missing the linker inputs to actually use that library.
If your project makes use of a declared entity from a third party (even if that usage is transitive through a dependency), it is required that the definitions from that third party library are included in the link step. This usually comes in the form of a static library, shared library/DLL, or even plain object files.
If the external library containing the definition in question is managed by
dds
, this issue should never occur. If the library exists outside of
dds
(e.g. a system library), then that library will need to be manually
added as a linker input using a toolchain file using the Link-Flags
option.
See: Toolchain Option Reference.
If the name of the unresolved symbol appears unfamiliar or you do not believe that you are making use of it, it is possible that one of your dependencies is making use of a system library symbol that needs to be part of the link. The link error will refer to the object/source file that is actually making the unresolvable reference. Seeing this filepath will be a reliable way to discover who would be making the reference, and therefore a good way to track down the dependency that needs an additional linker input. Refer to the documentation of the dependency in question to see if it requires additional linker inputs in order to be used.
If the library that should contain the unresolved reference is a dependency
managed by dds
, it is possible that the library author has mistakenly
declared a symbol without providing a definition. If the definition is
present in the dds
-provided dependency library, then the failure to resolve
the reference would be a dds
bug.
Issue: The definition is simply missing¶
C and C++ allow for an entity to be declared and defined separately. If you declare and entity but do not define that entity, your code will work as long as no one attempts to refer to that entity.
Ensure that the entity that is “missing” exists.
Issue: Missing virtual
method implementations¶
If the error refers to a missing vtable for class
, or if the error refers
to a missing definition of a virtual
function, it means that one or more
virtual
functions are not defined.
Note that virtual
functions are slightly different in this regard: It is
not required that someone actually make a call to the virtual
function for
the definition to be required. The metadata that the compiler generates for
the class containing the virtual
functions will implicitly form a reference
to every virtual
function, so they must all be defined if someone attempts
to instantiate the class, as instantiating the class will form a reference to
that metadata.
Issue: Mismatched declarations and definitions¶
Suppose you have a header file and a corresponding source file:
namespace foo {
size_t string_length(const string& str);
}
#include "a.hpp"
using namespace foo;
size_t string_length(const string& str) {
// ... implementation goes here ...
}
The above code will link correctly, as the definition of foo::string_length
,
is available from a.cpp
, while the declaration exists in a.hpp
.
However, if we modify only the declaration to use string_view
instead of
const string&
, something different occurs:
namespace foo {
size_t string_length(string_view str);
}
It may be tempting to say that “our declaration and definition do not match,”
but that is semantically incorrect: We have declared a function
size_t foo::string_length(string_view)
, but we have defined and declared
a completely different function size_t string_length(const string&)
!
The compiler will not warn about this: There is nothing semantically incorrect
about this code.
The linker, however, will not find any definition of foo::string_length
.
The function ::string_length(const string&)
isn’t even in the foo
namespace
: It was declared and defined at the global scope within
a.cpp
.
If you are seeing an error about an unresolved reference to a function that is declared and defined separately, and you are sure is being compiled, check that the signature (and name) of the definition and declaration match exactly.
Tip
In essence, the error originates from relying on the
using namespace foo
directive to cause the definition of
string_length
to incidentally hit the name lookup of the prior
declaration.
In C++, using a qualified name at the definition site can prevent this error from slipping through:
#include "a.hpp"
using namespace foo;
size_t foo::string_length(const string& str) {
// ... implementation goes here ...
}
By using the qualified name foo::string_length
at the definition site,
the compiler will validate that the function being defined has a prior
declaration that matches exactly to the signature of the definition.
Note that this is not the same as defining the function within a
namespace
block:
#include "a.hpp"
// NOT HELPFUL!
namespace foo {
size_t string_length(const string& str) {
// ... implementation goes here ...
}
}
This will suffer the same potential mistake as defining it with an unqualified name.
Note that within the scope of a function that has been declared within the
namespace, that namespace is currently within scope even if the definition
itself is not wrapped in a namespace
block. It may be a good option to
simply remove the using namespace
directive altogether.
Note
This trick cannot be applied to names that are declared at the global scope, since you cannot use the global-namespace qualifier at a function definition (it is not valid syntax):
// Declaration at global scope
void some_function();
// Definition? No: Invalid syntax!
void ::some_function() {
// ... stuff ...
}
Issue: The source file containing definition is not being included in the link¶
If the translation unit that contains the definition of an entity is not being passed to the linker, the linker will not be able to find it!
If you are using dds
correctly, and the compiled source file containing the
definition is placed as a (direct or indirect) descendent of the src/
directory, then dds
will always include that source file as part of the
link for the enclosing library.
Build systems that require you to enumerate your source files explicitly will not automatically see a source file unless it has been added to the source list. Even build systems that allow directory-globbing (like CMake) will need to have the globbing pattern match the path to the source file.