Meeting 149 - Visual Studio, GCC, sanitizers, parsers, crashes, UB

Media

Video

Podcast

Powered by RedCircle

Visual Studio 2022 17.2

Microsoft released VS2022 17.2. The main highlights for C++ developers are:

  • Modules support for CMake
  • Inline hints for deduced types and function parameters (like those CLion and ReSharper had for a few years now)
  • Improvements for RTOS and register views for embedded developers

C++20 support is complete in both VS2022 17.2 and VS2019 16.11.4. This includes the latest defect reports against C++20.

Comparing memory sanitizers

Julien Jorge wrote an article in which he compares memory sanitizers (unfortunately it’s on Medium so may be paywalled). Apart from the ones I knew – Address Sanitizer and Valgrind he lists some others that I didn’t know about, like Dr. Memory or Intel Inspector.

The author then used a set of buggy programs with a set of sanitizers, which, interestingly, included Memory Sanitizer (Clang). For best results you’d want to rebuilt the entire standard library to use it, which the author seemingly didn’t do. He ran the test set on several different OS and hardware configurations and produced a set of benchmarks. His summary is as follows:

The less efficient tool on this test set is Memory Sanitizer, which finds only two errors, also reported by other tools. Address Sanitizer is excellent. Its impact on the execution speed is low, it finds many errors, including errors not found by the other tools. Valgrind and Inspector 2020 are as good as each other. They find the same errors, including errors not found by Address Sanitizer. Both are also very slow, in the same order of magnitude. Inspector 2020 is the only good version of this tool. 2016 and 2017 could not manage to start the test; 2018 and 2019 report an error on the test where there is none. When running the tools on non-toy use cases the program ran 3 to 13 times slower with Address Sanitizer, and 163 to 565 times slower with Inspector or Valgrind.

In the end the author says that they switched to Valgrind in their CI (from Intel Inspector). Why not Address Sanitizer, I wonder, given his conclusion above? My guess is they wanted to check the existing release build and avoid having to produce a new instrumented build for ASan.

There are some useful hints in the Reddit thread, like the suggested ASan flags:

ASAN_OPTIONS=strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:detect_leaks=1

GCC 12.1 released

GCC 12.1 has been released. It celebrates 35 years since GCC 1.0 was released. The main highlights of this release are:

  • Initial support for CTF debug info format – looks mainly relevant for C, not C++. CTF = Compact C Type Format.
  • Improved C++20 support (still no modules though).
  • Improved experimental C++23 support. See the article on the RedHat blog for details.
  • Vectorization is now enabled at -O2 optimization level (since GCC 12).
  • Mold linker is now supported out of the box.

See the full change list for details.

Rui Ueyama tweeted:

Redditors are unhappy at the lack of Modules progress and the absence of support for std::format and std::chrono calendar support.

It looks like Nathan Sidwell is the only developer working on GCC Modules support. The whole compiler and language development situation is a mess, a redditor says:

Its always been bizarre to me how - despite C++ being one of the most incredibly widely used languages - the amount of manpower available to the language and ecosystem seems to be relatively low. Even the whole committee process is all done by volunteers with very limited time, its strange

Tristan Brindle says:

I do find it quite strange that given the amount of money in the C++ ecosystem – Big Tech, financial firms, etc – and given the increased developer productivity that would result from faster compile times, no-one seems to making modules a priority. Everybody wants it, but no-one wants to pay for it…. But Google or Apple could probably recoup the cost of a developer over the course of a year just in power savings from making Webkit and LLVM compile faster!

Regarding contributing to GCC development, this redditor says:

I’d love to contribute but the last time I browsed the gcc source code I opened a portal to hell.

Regarding Google contributions to compiler development, another redditor notes:

Folks here have said that Google stopped contributing to clang so much once they couldn’t get support for changing/breaking the ABI. Now they do stuff with libraries like Abseil instead. They’ve also been doing a bit more with Rust.

Not very encouraging.

Here is another Reddit thread on modern C++ in GCC 12.

lexy: C++ parsing DSL

Jonathan MΓΌller wrote lexy: a parser combinator library for C++17. It uses a C++ DSL to describe the parser instead of an external grammar. It allows embedding of the grammar directly into a C++ program. Lexy supports Unicode, allows you to use your own data structures for parsing results, can parse at compile time (constexpr) and is capable of parsing both text and binary data.

Example IPv4 parser:

 1namespace dsl = lexy::dsl;
 2
 3// Parse an IPv4 address into a `std::uint32_t`.
 4struct ipv4_address
 5{
 6    // What is being matched.
 7    static constexpr auto rule = []{
 8        // Match a sequence of (decimal) digits and convert it into a std::uint8_t.
 9        auto octet = dsl::integer<std::uint8_t>;
10
11        // Match four of them separated by periods.
12        return dsl::times<4>(octet, dsl::sep(dsl::period)) + dsl::eof;
13    }();
14
15    // How the matched output is being stored.
16    static constexpr auto value
17        = lexy::callback<std::uint32_t>(
18            [](std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d) {
19            return (a << 24) | (b << 16) | (c << 8) | d;
20        });
21};

Lexy is similar to other PEG parsers, like Boost.Spirit. The main difference is that lexy doesn’t do any implicit backtracking, thus giving the programmer more control over parsing.

There is a nice online playground available which illustrates how lexy works, including the resulting graphs.

The library is on GitHub under BSL-1.0 licence.

C++ Debugging in Visual Studio Code

A new article has been posted on the Microsoft Blog in which Julia Reid describes C++ debugging support in Visual Studio Code. See also Reddit. The main news are:

  • Native support for Apple’s M1 chip
  • Data breakpoints when debugging with GDB (as it uses hardware breakpoint support the number of data breakpoints is limited to around 4)
  • Quick Run/Debug button that allows running/debugging a single C++ file without having to edit launch.json file

Nice to see steady improvements to one of the most popular code editors/IDEs.

A useful tip about cppreference.com from Victor Ciura

Worst things about C++

Reddit

The main pain points from the thread:

  • Setting up build system and tools, especially for a new cross-platform project
    • cmake-init is a tool to initialize a new CMake project
  • Backward compatibility (also the best thing)
  • ABI stability, or “if only we had epochs like Rust”
  • Bad teachers, bad books, bad tutorial sites run by content farms, and bad videos resulted from Dunning-Kruger Effect. =>
  • C developers who constantly shit talk C++ features as if someone is going around putting guns to their heads and threatening to take away C. =>
  • Long build times
  • Lack of built-in package manager
  • Header files
  • Lack of Unicode support
  • Slow standardization process
  • The amount of pitfalls that you have to remember to avoid.
  • It is baffling to me that you can write ill-formed code that may or may not not give you any errors at compile-time, and the program may do something but what it will do is undefined. =>
  • People who complain about C++. Also the enthusiastic Rust people who show up on C++ fora. Rust is its own thing, better in some ways and not as good in others. =>

And the winner is:

  • The monthly “what do you hate about C++” thread here on /r/cpp is the thing I hate most. =>

How to crash a C++ program

A redditor asks: “What’s your favourite way to crash your application?”

Interestingly, his example is not guaranteed to crash, as we saw earlier - dereferencing a null pointer is UB and not necessarily a crash.

1void Kaboom() {
2    *(int*)0 = 0;
3}

As another redditor states:

I’ve worked on at least one platform where writing to address 0 does not crash the program. The optimizer is also free to remove memory writes that are never referred-to later.

Their example is:

1auto volatile bad_ptr = (double *)(-9ll); // Unaligned and near end-of-page
2bad_ptr[0] = (6.022141e23 / 0) + bad_ptr[1];

They say:

But most of the time, I’m boring and use std::abort().

They also explain why the Avogadro number was chosen for this code snippet:

  1. It’s well-known and has nothing to do with systems programming. So long as the program itself doesn’t have anything to do with chemistry, it’s a sign that Something Strange is going on.
  2. It’s too large for the compiler to emit it as an immediate, so it must be loaded from the data segment.
  3. It’s a floating-point number. so x86 can’t divide it with idiv; it has to get transferred to the FPU (or SIMD unit), and they’re really picky about alignment so the addition will bomb.
  4. The goal here is to crash even if FP exceptions are disabled by forcing a bad memory access that the compiler can neither inline (with immediates) or elide (because it’s marked volatile) with the optimizer turned on. Doing just what OP did will often be removed by compilers running with -Os optimization.

Some other choices:

  • std::terminate(); std::abort(); std::raise(SIGTERM); std::exit(-1); std::_Exit(-1); =>
  • Deferred crashing: __asm volatile(" mov sp, #0x0\n"); =>
  • [[clang::nomerge]] __builtin_trap(); with the added bonus: “This produces a 2 byte UD2A instruction on x86_64, and the attribute prevents optimizers from merging it together, so you get distinct source locations in your crash reports.” =>
  • On ARM microcontrollers this will crash before memory is accessed =>:
1// Call an invalid function pointer
2// (LSB=0 means trying to enter the deprecated "ARM state";
3// this will crash the CPU before even trying to access the memory at address 2)
4((void (*)(void))2)();

Some commenters suggest using asm int 3 or __debug_break() which breaks into debugger or crashes if there is none.

And the winning comment on how to crash a program is:

Just running it usually does the trick.

Box2D

Box2D is a feature rich 2D rigid body physics engine for game programming.

It has been used in many games, including Crayon Physics Deluxe, winner of the 2008 Independent Game Festival Grand Prize.

Box2D was created by Erin Catto of Blizzard (soon to be part of Microsoft) as part of a physics tutorial at the Game Developer Conference.

Since Box2D is written in C++, you are expected to be experienced in C++ programming. Box2D should not be your first C++ programming project! You should be comfortable with compiling, linking, and debugging.

A fair warning.

Box2D is on GitHub under MIT licence. It builds using CMake on Windows with Visual C++ and can be installed via vcpkg. Ports are also available for Flash, Java, C#, Python. It has its own subreddit and a Discord server.

Mysterious Memset

Colby Pike wrote an article illustrating the effect of UB on compiler optimisations.

The following code:

1void chop(int* count, std::string& str)
2{
3    for (int i = 0; i < *count; ++i) {
4        str[i] = 0;
5    }
6}

optimises into:

 1void chop(int *count, std::string *str)
 2{
 3  if (*count > 0) {
 4    long idx = 0;
 5    do {
 6      str->__data[idx] = 0;
 7      idx += 1;
 8    } while (idx <= *count);
 9  }
10}

But if we use std::u8string as the parameter type, the result is:

1void chop(int *count, std::u8string *str)
2{
3  if (*count > 0) {
4    std::memset(str->__data, 0, (size_t)*count);
5  }
6}

This is because assigning to elements of a std::u8string cannot affect the passed count, since aliasing with anything other than char* is UB. But in case of std::string the compiler must assume that aliasing is possible and assigning to string elements may change the count, so there can be no assumptions of the number of loop iterations.

Twitter: demand for programmers