Why would anybody do this? What is “non-existent” code?
In your team, perhaps struggling with this large legacy code, one day, a warning is switched on and used in all the code. By decree.
|
1 |
#pragma GCC diagnostic warning "-Wunused-parameter" |
The decision has been made to warn on unused parameters in thousands of functions that massive body of code might contain. Warning on unused variables is on by default already.
Suddenly, that massive body of legacy code produces thousands of warnings when compiled. And that takes long minutes. Not to fret, the “standard” macro is used to transform those countless unused parameters into non-existent code. So the developer can see them for future reference, but the compiler is not slowing things down. That is now the code that compiles into zero bytes, you think. It is non-existent in the resulting executable. Temporarily unwanted code: legal (so we know it compiles), but non-existent.
Well, that was simple, say you, there is this well-known, mature and universally used macro that does make unused code “get out of the way”
|
1 2 3 |
// de-facto C/C++ standard // I can see that 'x' but I am not going to use it right now #define UNUSED(x) (void)x |
If this macro makes you ask questions, please read on.
Compile times matter, and the compilers throw this annoying “unused variable… ” or “unused parameter …” if asked, whenever (for example) you are writing the code and you still have not fully developed the innards, but just the signatures and empty implementations.
|
1 2 3 4 5 6 7 |
#pragma GCC diagnostic warning "-Wunused-parameter" // now parameters are unused and warned about std::string clever_function( int a1, bool a2, float a3 ) { std::string retval_ ; return retval_ ; } |
You WANT to have those parameters there. And there will be warnings on unused parameters now. You dutifully oblige, and you sprinkle your code with the famous UNUSED macro or some such macro, available in your DEVENV environment.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#pragma GCC diagnostic warning "-Wunused-parameter" // now parameters are unused but now warned about // the logic is they are VISIBLE // and to be used in some future std::string clever_function ( int a1, bool a2, float a3 ) { static std::string retval_ ; UNUSED(a1); UNUSED(a2); UNUSED(a3); return retval_; } |
The questions and observations about the above, not often asked:
- In the next, perhaps distant, development cycles, you or someone else will have to remove all these UNUSED statements
- Why is the compiler (not)throwing warnings about unused things?
- For historical reasons
- To help you not have unused things in developed code
- Unused things are taking up bytes to store in the final executable
- Modern compilers warn on this only if asked to, that is, if the warning level is above the default.
- In the case of MSVC, the default is
/W3
- In the case of MSVC, the default is
- The modern compiler automatically replaces the unused code with zero bytes.
- It is “optimised away”
So basically, you are advised to be sure your “currently unused” code is visible, it compiles and gets out of the way. You do that because it is good for some code to be seen and be correct, although at present, unused. But why should you care at all HOW is that done?
The big revelation
The key issue. The statement:
|
1 |
(void)x ; |
Casting is not a “zero effort” operation.
Above, evaluates the ‘x’ before casting it to void. So if you use this ‘standard solution’ in your standard, you might evaluate all sorts of function calls, instantiations or whatever, before casting them to (void). And that will happen at compile time, increasing it.
|
1 2 3 4 |
// standard code // evaluate x first, cast 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). So, how do you know what the side effects of(void)x ; , are? Plural. Is it that maybe a type of x, is some 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 compiler and let it just remove the unused code. Do not hide things behind an UNUSED macro, please.
Tarot for today. A resurrection of countless bugs awaits you
We concluded that some of you do want to see the code in your IDE, which 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.
|
1 2 3 4 5 6 7 8 9 10 |
// // actual real life example int clever_function ( arg_type_1 a1 ) { some_library_type retval_[BUFSIZ] ; UNUSED(a1); UNUSED(retval); // *temporarily* commented out // LOG( __function__, __FILE__, __LINE__); return 0; } |
The developer above wanted to “Just write it and comment it out”. Perhaps. That is not good because it hides syntax mistakes and such. It will be a royal pain (for the inheritor) to comment that thing in, a few years down the line, have the syntax error pop up, and then redo, debug and such.
Example. What if, for the LOG call above, the order of arguments is wrong? Well, someone else (or is it you?), will find that out after that line is commented in. After many years, perhaps. And another error: 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
|
1 2 3 4 5 6 7 8 |
#if defined (__cplusplus) // for variables and expressions // guranteed no evaluation // guaranteed zero bytes overhead // guarantedd compiler will notice if not OK to compile // can be pleaced in any scope #define DBJ_REMOVE(...) static_assert( (noexcept(__VA_ARGS__),true) ) #endif |
That is a standard C++ (variadic) macro. This is not C. This is standard C++. noexcept operator (not the specifier) does not evaluate its argument. And there are no exceptions (pun intended) 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 existing “UNUSED” macros cannot be placed in any scope, but only where the macro expansion will produce properly rendered qualified or unqualified, standard names like the semi-famous MSVC UNREFERENCED_PARAMETER(P) macro. As its name suggests, 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. And it leaves zero byte overhead, without evaluating the expressions ‘removed’.
And remember: it will not allow you to leave unusable, bug-ridden 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 is possible and advisable to use just in some limited number of use cases (definitely not in the global scope), on its own. And it will, sadly, always evaluate its argument.
What about [[maybe_unused]]?
Is the standard C++ attribute that suppresses the warnings on unused entities. Where you now immediately see the crucial issue: it does not stop the compiler from evaluating 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.
|
1 2 3 4 5 6 7 8 9 10 |
// where to place [[maybe_unused]] in this case? auto [a,b,c] = make_compound_type () ; auto d = a + b; // this will not compile // [[maybe_unused]]c ; // let's just use our macro // this will make it obvious but will be // a zero byte overhead *and* // will not evaluate the c DBJ_REMOVE(c); |
That was the standard C++. Do not build unsafe code. Even if some “standard” macro seems like the logical solution.
And what about the C programming language?
C, what C? Did not Bjarne said C is obsolete?
Wait! Stop! Only kidding :) C is not obsolete, and the amount of (mission-critical) C code in action on this planet is staggering. So, we have to oblige.
There are C macros made for not the same, but like purpose, and better than the “de facto standard” (void)x .
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, here is perhaps the best trick. With a slight variation from me:
|
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 |
// in your favorite top-level header // first pacify the clang or gcc compiler #pragma GCC diagnostic ignored "-Wsizeof-array-argument" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" //---------------------------------------------------------------------------------------- #define dbj_CONCAT_IMPL(x, y) x##y #define dbj_CONCAT(x, y) dbj_CONCAT_IMPL(x, y) //---------------------------------------------------------------------------------------- #if !defined(__cplusplus) #define dbj_UNUSED(expr) typedef char dbj_CONCAT(__static_assert_t_, __COUNTER__)[sizeof(expr) != 0] #else #define dbj_UNUSED(...) static_assert( (noexcept(__VA_ARGS__),true) ) #endif // need to switch it on #pragma GCC diagnostic warning "-Wunused-parameter" int fun (int alone ) { dbj_UNUSED(alone); return 0 ; } int main (const int argc, char * argv[static argc]) { dbj_UNUSED(argv); int will_be_used = 5; dbj_UNUSED(will_be_used); return 0; } |
Yes, that is it. For both C and C++. With those clang/gcc warnings silenced. I switch them off for a global scope. If you want them back, you can see how in the comments. Yes, with a little help from the good old sizeof() operator, C expr is not being evaluated. Its result is used only. The usage is also above.
I think, that a pretty good solution. Covering all the C /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 code. Build safe.

Do not leave unsafe C++ builds behind.
[Updated on 2 May 2025] [Published on: 5 April 2019]