C++ Diagnostics and Debugging Utilities

Some tools and techniques for detecting, reporting and investigating errors and unexpected behavior in C++ code.


Pre-Processor Macros

An old method of getting information about the file and line where a thing occurred applied pre-processor macros:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
 
void log (const char* file, int line, const char* msg)
{
    std::cout << file << " (" << line << "): " << msg << '\n';
}
 
void func(int x)
{
    log(__FILE__, __LINE__, "Message from a different function");
}
 
int main(int, char*[])
{
    log(__FILE__, __LINE__, "Hello world!");
    func(2);
}

Output:

/xyz/example.cpp (15): Hello world!
/xyz/example.cpp (10): Message from a different function

std::source_location

Since ISO C++ 2020, there is a new and better way of getting this information: std::source_location

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <source_location> // Requires ISO C++ 2020!
#include <string_view>
 
void log (const std::string_view msg,
          const std::source_location loc = std::source_location::current())
{
    std::cout << loc.file_name() << " (" << loc.line() << ":" << loc.column() << "): "
              << loc.function_name() << ": " << msg << '\n';
}
 
void func(int x)
{
    log("Message from a different function");
}
 
int main(int, char*[])
{
    log("Hello world!");
    func(2);
}

Output:

/xyz/example.cpp (19:5): int main(int, char **): Hello world!
/xyz/example.cpp (14:5): void func(int): Message from a different function

std::stacktrace

Another tool that may be handy sometimes: std::stacktrace.
That requires ISO C++ 2023 support (and at the time of writing, also linking with sometimes different libraries).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <print>
#include <stacktrace>  // Requires ISO C++ 2023 & library linking!
#include <string_view>
 
void log (const std::string_view msg,
          std::stacktrace st = std::stacktrace::current())
{
    std::println("\n=========================================");
    std::println("Stacktrace (complete):\n{0}Message: {1}", st, msg);

    std::println("\n-----------------------------------------");
    std::println("Stacktrace (individual elements of each entry):\n");
    
    for (auto i = st.begin(); i != st.end(); ++i)
    {
        std::println("{0} - File: {1} (Line: {2})", i->description(), i->source_file(), i->source_line());
    }
}
 
void func(int x)
{
    log("Message from a different function");
}
 
int main(int, char*[])
{
    log("Hello, world!");
    func(2);
    return 0;
}

Output:

=========================================
Stacktrace (complete):
   0# main at /app/example.cpp:30
   1#      at :0
   2# __libc_start_main at :0
   3# _start at :0
   4# 
Message: Hello, world!

-----------------------------------------
Stacktrace (individual elements of each entry):

main - File: /app/example.cpp (Line: 30)
 - File:  (Line: 0)
__libc_start_main - File:  (Line: 0)
_start - File:  (Line: 0)
 - File:  (Line: 0)

=========================================
Stacktrace (complete):
   0# func(int) at /app/example.cpp:25
   1# main at /app/example.cpp:31
   2#      at :0
   3# __libc_start_main at :0
   4# _start at :0
   5# 
Message: Message from a different function

-----------------------------------------
Stacktrace (individual elements of each entry):

func(int) - File: /app/example.cpp (Line: 25)
main - File: /app/example.cpp (Line: 31)
 - File:  (Line: 0)
__libc_start_main - File:  (Line: 0)
_start - File:  (Line: 0)
 - File:  (Line: 0)

static_assert() and assert()

Assertions are helpful features for testing/debugging and for noticing errors/bugs that should never happen.

An assertion doesn’t allow to recover from an error (which, after all, should never had happenend in the first place).

static_assert()

A keyword/declaration that checks a software assertion at compile-time.
If the specified constant expression is false, the compilation fails with an error and displays the specified message (if one is provided).

static_assert(bce, msg); // Available since C++11.
static_assert(bce);      // Since C++17, the message parameter is optional.

assert()

A function-like pre-processor macro that checks a software assertion at run-time.
Evaluates an expression and if the result is false, aborts/terminates the program (and usually prints a diagnostic message).

// #define NDEBUG // uncomment to disable assert()
#include <cassert>
assert(condition);

There is no standardized interface to add an additional message to assert errors, but there are workarounds:

assert((2 + 2 == 4, "Message")); // -> 1.
assert(condition && "Message");  // -> 2.
  1. A portable way is to use a comma operator (provided it has not been overloaded).
  2. Combining a string literal (which always evaluates to true) by logical AND with the condition doesn’t impact the evaluation of the assert.