A graphical depiction of the steps in building a C++ executable, basics

Raymond Chen

One of the things you have to know when trying to diagnose a build failure is understanding what each step of the build accomplishes, so that you can fix the problem at the correct step.

Here’s the basic idea.

 
    .h, .cpp
   
    C++ compiler
   
    .obj
 





librarian  
 
.lib  
 
    linker
   
    .dll, .exe

Given a bunch of header files (.h) and C++ source files (.cpp), the C++ compiler produces a corresponding set of object files (.obj).¹ If your project builds a library, then the object files are given to the librarian to produce a library file, and that’s your project. In other words, your library project uses only the top part of the diagram, through to building the .lib file, but doesn’t go down the other branch that leads to the linker.

    .h, .cpp
   
    C++ compiler
   
    .obj
   
librarian  
   
.lib    

If your project builds an executable module (a .dll or .exe, for example), then the object files and any input libraries are given to the linker, which then generates the desired module file. In that case, you’re using the other branch of the diagram that leads to the linker:

    .h, .cpp
   
    C++ compiler
   
    .obj
   





   
   
.lib  
 
    linker
   
    .dll, .exe

Already you know enough to solve this problem:

My solution consists of three projects: A core library, a program that consumes the library, and a unit test that consumes that same library. I added some code to the core library, and now the program and unit test are generating linker errors due to a missing import library used by the code I added. I added that library to the core library’s Additional­Dependencies, but that didn’t fix it.

Next time, we’ll expand this diagram to include additional tools you may encounter in Windows projects.

Answer to exercise: The error complaining about the unresolved external symbol is coming from the linker, so you need to provide the library with that symbol to the linker. The core library project doesn’t run the linker: It runs the librarian to produce corelibrary.lib. It’s the program and unit test that consume the corelibrary.lib and produce contoso.exe and contoso_unittest.exe, respectively. Those are the projects that need the new library listed in the Additional­Dependencies.

To avoid having to update both the program and unit test each time the build requirements of the core library change, you might put those special configuration settings in a separate file that is included by the program and unit test projects, so that any changes need to be made in only one place.

¹ For unix, the same principles apply, but the file extensions are different. The extension for object files is .o, the extensions for library files are .a and .so, depending on what type of library they are. And unix executables traditionally have no extension.

5 comments

Discussion is closed. Login to edit/delete existing comments.

  • Mason Boswell 1

    Although a nice library that’s going to have hidden dependencies might save its users some time by sending a hint to the linker via #pragma comment( lib, “dependency.lib” ), as Microsoft does with many of its frameworks (MFC, for example).

  • 紅樓鍮 1

    Usually this should be something a build system handles for you, and CMake correctly handles static libraries that link to dynamic libraries.

  • Tom Lint 2

    Isn’t there an option “link library dependencies” or something, to have the build system automatically do this without having to modify anything else? I remember only having to set this option once, I believe as part of the project reference, and never having to care about adding additional dependencies to the library project down the road.

  • BCS 0

    Any build system or IDE that doesn’t natively understand transitive dependence and (at least by default) propagate them isn’t a very good tool. That might have been acceptable back in the days of make (which is little more than a glorified shell script), but there are multiple off-the-shelf options that make this problem almost impossible to provoke.

    • LB 0

      The default in Visual Studio is still to provoke this behavior, and as the previous commenter explained there’s a setting you have to change away from its default to get the more intuitive behavior.

Feedback usabilla icon