C++ How to produce legal and non-existent code

Why would anybody do this? What is “non-existent” code? That is the code that compiles into zero bytes. It is non-existent in the resulting executable. So, in C++, how to make temporarily unwanted code legal (so we know it compiles); but non-existent?

You might want it back sometime shortly.  Well, that is simple, say you, there is this well-known, mature and universally used macro that does make unused code “get out of the way”

// de-facto C/C++ standard
#define UNUSED(x) (void)x

If this macro makes you ask questions please read on, otherwise, leave in peace.

So, the first question again: why make some code “unused” in standard C++ code, compiled using modern compilers these days?

Primarily because, some of them still compilers throw this annoying “unused variable… ”  or “unused parameter …”, whenever (for example) you are writing the code and you still have not fully developed innards, but just interfaces.

std::string clever_function
( int a1, bool a2, float a3 ) {
std::string retval_ ;
}

On seeing this, a slightly older compiler screams at you about unused arguments and variables. You dutifully oblige and you sprinkle your code with the famous UNUSED macro or some such macro, available in your DEVENV environment.

char * clever_function ( int a1, bool a2, float a3 ) {
static char retval_[BUFSIZ] ;

UNUSED(a1);
UNUSED(a2);
UNUSED(a3);
UNUSED(retval);
}

The questions and observations about the above:

  • In the next development cycle, you or someone else will have to remove all these UNUSED statements
  • Why is the compiler throwing warnings about unused things?
    • For historical reasons
    • To help you not have unused things in developed code
  • Modern compilers warn on this just if asked to, that is if the warning level is above default.
    • In the case of MSVC, the default is /W3
  • The modern compiler automatically replaces the unused code with zero bytes.

So basically you are advised to be sure your “currently unused” code compiles but gets out of the way.

And now the big revelation

The key issue.  The statement:

(void)x ;

Evaluate the ‘x’ before casting it to void.  So if you just use this ‘standard solution’ in your standard C++ code, you might be evaluating all sorts of function calls, instantiations or whatever, before casting them to (void).

// standard C++ code
// evaluae x first, casto to void second
// but what is x?
(void)x;

In the case of C++, the type of x above can be any fundamental, compound or UDT (user-defined type).

How do you know what is the side effect of (void)x ; 

if a type of x is a global instance of some complex type from some external library?

The last thing you want in your code is silent evaluations

In this day and age, the best course of action is to simply use the standard modern C++ compiler and let it just remove the unused code. Do not hide the things behind an UNUSED macro, please.

Also, an important note.

// this is not a syntax error
// value of expression X
// has silently dissapeared
// if evaluated
if ( bool_expression ) X ;

If the compiler is set to warn on unused values it will warn you on the above, otherwise, it won’t. You have been warned.

A resurrection of countless bugs

What if you do want to see the code in your IDE, that is going to be required but you do not want it in action just yet? You want to see it, but you do not want it to get in the way.

 

The developer above, wanted to “Just write it and comment it out”? Perhaps. That is not good because it will inevitably hide syntax mistakes and such.  It will be a royal pain to comment that thing in, later down the line, have the syntax error, and then retype, debug and such.

What if for the LOG  call above, the order of arguments is wrong? Well, someone else (or is it you?), will find out after that line is commented in.  And by the way, it is not ‘function‘ but ‘func‘. And so on…

In real-life software development (thousands of functions) this kind of “we will fix it after commenting it back in” event, can be a real risk. And a naturally rich source of the resurrection of countless bugs afterwards.

It is much better to check immediately if something compiles, even if it is not immediately required.

I have written a little macro that helps

That is a standard C++ (variadic) macro. This is not C. This is standard C++.  noexcept the operator does not evaluate its argument. And there are no exceptions to that and no legacy issues like with the sizeof(x) operator that many of you would instinctively use.

And most importantly, static_assert() can be placed anywhere too.  in the global space, in the namespace, in the function in the class, just anywhere. That is: in any scope.

Almost all existent “UNUSED” macros simply cannot be placed in any scope, but only where the macro expansion will produce properly rendered qualified or unqualified, standard C++names. Like the semi-famous MSVC UNREFERENCED_PARAMETER(P) macro.  As its name says it is made only for unreferenced parameters. To be used in the function scope.

Note: What are “unqualified names” or “qualified names”? If curious read on the subject here. That means you can not just willy-nilly place these “de-facto standard” macros wherever required. That will simply not compile.

To see DBJ_REMOVE in action please study the online proof of concept.

You will notice immediately, that it is almost all, just in the global scope.  DBJ_REMOVE can be placed anywhere and it takes “anything”. Crucially, as long as it is normal and proper C++ code.  Any variable or expression will have to be a proper C++. And it leaves zero byte overhead, without evaluating the expressions ‘removed’.

And remember: it will not allow you to leave unusable code behind.

Feel free to play with that online IDE (Godbolt). Include any standard header … Try it with anything you like, function calls, array index moves, pointer arithmetics or whatever else you fancy, and observe the resulting assembly on the right. It is zero.

The “standard” UNUSED kind-of-a macro will be possible to use just in some limited amount of use cases (definitely not in the global scope) on its own. And it will sadly, always evaluate its argument.

[[maybe_unused]]

Is the standard C++ attribute that suppresses the warnings on unused entities.  Where you now immediately see the crucial issue: it evaluates things.

Sometimes you might need that. In that case, use the standard attribute. But beware, it has somewhat cumbersome placement rules. And sometimes it is simply unclear where to place it.

 

Enjoy the standard C++. Do not build unsafe code. Even if the macro is the solution.

And what about C?

C, what C? Did not Bjarne say C is obsolete?

Wait! Stop! Only kidding :) C might be obsolete but the amount of (mission-critical) C code in action on this planet is staggering. So, I have to oblige.

There are C macros made for the same purpose but better than the “de-facto standard” (void)x . As an example here is one from the good UCRT team as present in the latest SDK we have today in the file corecrt.h

// Windows SDK 10.0.17134.0
// corecrt.h

#define _STATIC_ASSERT(expr) typedef char __static_assert_t[(expr) != 0]

I am sure you can see the trick used and you can also, as trained by me by now, see the flaw: expr is being evaluated so that it is compared with zero above.

to cut the (very) long story (very) short I have seen loads of macros for C for the same purpose. For C99, C11 and so on. And yes this is indeed the best trick. With a slight variation from me:

// this is C
#if ! defined(__cplusplus)
#define DBJ_REMOVE(expr) typedef char __static_assert_t[sizeof(expr) != 0]
#endif

Yes with the little help of the good old sizeof() operator, expr is not being evaluated. Its result size is used only.

For C this is a pretty good solution. Covering all the C standards in use today too.  Although not perfect. If interested in the caveats perhaps this is a good starting point.

As I already said: Enjoy the standard C++. Do not build unsafe code.

Do not leave unsafe C++ builds.

Do not leave unsafe C++ builds behind.

[Published on: 5 April 2019]

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.