totally revamped, still TOTALly TRASH

Turn On Compiler Warnings!

{% include toc %}

This post originally appeared on DevCafe

Compiler warnings can be a nuisance, clogging the output with unnecessarily verbose information. This is especially true of C++, even more so when the code uses template magic. However, turning them off can be rather harmful.

TL;DR

Set -Wall -Wextra, or equivalent, as your basic compiler flags, both in debug and in release mode. If you are using CMake, the Autocmake project does it by default

An example from the cnpy library

Consider the following C++11 example, but it applies equally well to earlier standards. The cnpy library for saving/loading C++ arrays to/from NumPy binary format has a basic data structure, called NpyArray. An object of this type is constructed by supplying:

  • the shape of the array, i.e. a std::vector<size_t>. For example, a 2-dimensional array with dimensions 10 and 11 will have: std::vector<size_t> shape({10, 11});
  • the word size of the data type to be dumped to NumPy array. This is either read from the .npy when loading the array, or determined by the result of sizeof(T), where T is the data type, when saving the array.

The constructor will then compute how large a memory buffer is needed and allocate a std::vector<char>. The number of values in the array is computed from the shape array:

or more compactly using std::accumulate:

The type information is encoded in the .npy file format header. When loading the array the user will have to perform a reinterpret_cast to get the correct data type.

Runtime error!

Stripped to its barebones, the NpyArray class looks like this:

Let’s now try to compile it:

The live example on Coliru shows that we get a runtime error because the assertion in the constructor fails.

What’s happening? Well, a very, very stupid mistake. The buffer_ data member is initialized using the nValues_ data member This shouldn’t be a problem, since it’s initialized first in the constructor, right? Wrong! According to the standard, 12.6.2 Section 13.3 data members are initialized in the order they were declared in the class. Thus buffer_ gets initialized first, using an undefined value for nValues_.

Fixing it

The correct struct declaration is thus:

which also honors the tenet of ordering data members in your classes and structs by their size in memory. However, this is something very easily forgotten. How to avoid these kinds of errors?

  1. Do not initialize data members based on other data members. This is, in my opinion, overly restrictive.
  2. Insert assertions in the constructors. Very useful, but assertions only work when -DNDEBUG is not given to the compiler. Most of the times this is not the case when compiling with optimization.
  3. Turn on compiler warnings. -Wall catches this mistake and many others. For an extra layer of warnings, I also turn on -Wextra. This is the output on Coliru