Skip to main content

C++ Initialization Subtleties (significance of -Werror compiler option)

Modern C++ continuous integration build pipelines might produce huge logs which may be easily overlooked. Among other errors/warnings, a potential risk caused by invalid narrowing assignments might be lurking in those dark corners... This write up is a little reminder about an essential feature in modern C++ compilers and can help defend against that specific problem.

Prior to C++11, the following was a valid assignment and still is even in C++20 or higher ...

unsigned int unsignedSinceBirth = 10;
unsignedSinceBirth = -10;  // assigning a negative value to an unsigned container

printUnsignedVal(unsignedSinceBirth);

which when compiled using these options "g++ -std=c++20 <cpp files> -o <executable-name>" does not even emit a warning. And an executable is generated successfully.

The output of running that code is an arbitrary unexpected value.

Modern C++ (post Cpp11) allow uniform initialization syntax which can help the compiler detect this situation as follows:

unsigned int unsignedSinceBirth = 10;
unsignedSinceBirth = {-10};  // assigning a negative value to an unsigned container
printUnsignedVal(unsignedSinceBirth);

Compiling this revised code fails and that's a good thing. G++ emits this error:

error: narrowing conversion of ‘-10’ from ‘int’ to ‘unsigned int’ [-Wnarrowing]

But when the assignment happens in the following manner, compiler only emits a warning and the compilation still succeeds:

unsigned int unsignedSinceBirth = 10;

// .. somewhere else in the code

int rougeInteger = -10;
unsignedSinceBirth = {rougeInteger}; 
printUnsignedVal(unsignedSinceBirth);

Compiling the revised code does not fail and that's a bad thing. G++ only emits a warning:

warning: narrowing conversion of ‘rougeUnsignedNum’ from ‘int’ to ‘unsigned int’ [-Wnarrowing]

In absence of eagle eyes, that warning will most likely go unnoticed in large build logs and the compilation will succeed. It turns out there is a way to enforce the compiler detect such assignments.

If we include "-Werror" option in the G++ command line as follows:

 "g++ -std=c++20 -Werror <cpp files> -o <executable-name>"

The complier will emit an error and compilation will not be successful and that's the effect we were looking for.

Crux: Always pay attention to what compiler is trying to tell us. If it's not telling, force it to say something :)

Note: Compilation using LLVM clang as follows complains with an error without needing an explicit option:

 "clang -std=c++20 <cpp files> -o <executable-name>"

with the following error:

error: non-constant-expression cannot be narrowed from type 'int' to 'unsigned int' in initializer list [-Wc++11-narrowing] 

Comments

Popular posts from this blog

C++ logging using Apache Log4cxx on Linux

I'd demonstrate the set up of Apache log4cxx on Ubuntu 12.x in following steps: 1. Download all required packages 2. Build Log4cxx via Apache Maven 3. Use the libraries in a NetBeans C++ project on Ubuntu Linux. Reference URL: http://www.yolinux.com/TUTORIALS/Log4cxx.html This URL is quite detailed and explains things for other *nix operating systems as well. I wanted to start with minimum steps required, hence this post. I have a Windows 7 desktop and have Ubuntu 12.x 64-bit running on it via Oracle Virtualbox. So Ubuntu is running as a guest on my Windows 7 host. [The reference URL mentions different versions of  'libaprXX' libs but we have to use 'libapr1-dev' and 'libaprutil-dev', will see that later.] Prerequisites (install all of the following one by one) autoconf and automake --> sudo apt-get install autoconf automake libxml2 and libxml2-devel ( http://xmlsoft.org/ ) --> sudo apt-get install libxml2 libxml2-dev gmp ( ...

C++11 std::thread Construction and Assignment Tricks

C++11 arrived with std::thread and related classes built within the standard. With the move semantics in place, the construction and assignment practices of 'std threads' deserve a small write up. Let's define a 'Runnable' first: class Runnable {    public:       virtual void operator ()() = 0;       virtual ~Runnable(){} }; class Task : Runnable {      public:          void operator ()()          {               //do sth here..          } };                                           ...