Will a "variableName;" C++ statement be a no-op at all times?
Asked Answered
F

2

21

In C++ sometimes a variable will be defined, but not used. Here's an example - a function for use with COM_INTERFACE_ENTRY_FUNC_BLIND ATL macro:

HRESULT WINAPI blindQuery( void* /*currentObject*/, REFIID iid, void** ppv, DWORD_PTR /*param*/ ) 
{
    DEBUG_LOG( __FUNCTION__ ); //DEBUG_LOG macro expands to an empty string in non-debug
    DEBUG_LOG( iid );
    iid; // <<<<<<<----silence compiler warning
    if( ppv == 0 ) {
        return E_POINTER;
    }
    *ppv = 0;
    return E_NOINTERFACE;
}

In the above example iid parameter is used with DEBUG_LOG macro that expands into an empty string in non-debug configurations. So commenting out or removing the iid variable name in the signature is not an option. When non-debug configurations are being compiled the compiler spawns a C4100: 'iid' : unreferenced formal parameter warning, so in order to silence the warning the iid; statement that is believed to be a no-op is added.

The question is the following: if we have any of the following declarations:

 CSomeType variableName; //or
 CSomeType& variableName; //or
 CSomeType* variableName;

will the following statement in C++ code:

variableName;

be a no-op at all times independent of what CSomeType is?

Forayer answered 27/10, 2010 at 7:45 Comment(7)
Won't this trigger a "statement has no effect" warning?Nude
@roe: It doesn't on Visual C++. Maybe it does on some other compilers.Forayer
If between the declaration and the statement you abuse the preprocessor it could have an effect. For example #define variableName exit(-1)Cerell
Also you cant declare reference without initialization. So you will get "error C2530: 'variableName' : references must be initialized" error. However, you can use UNREFERENCED_PARAMETER macro which is defined in winnt.h.Aurelio
@Benoit: it's not a variable name anymore at that point, duh.Cordiacordial
Looking at the options you could just wrap the declaration in a macro as well. This takes care of the problem "..., REFIID ONLY_DEBUG(iid), ..." Then in debug mode you can omit the name of the parameter.Kasiekask
yes, gcc would produce a 'statement has no effect' warningPappus
R
52

Yes, but you'll likely get another warning.

The standard way of doing this is: (void)iid;.


Very technically, this could still load iid into a register and do nothing. Granted that's extremely stupid on the compilers part (I doubt any would ever do that, if it does delete the compiler), but it's a more serious issue if the expression to be ignored is something concerning observable behavior, like calls to IO functions or the reading and writing of volatile variables.

This brings up an interesting question: Can we take an expression and completely ignore it?

That is, what we have now is this:

#define USE(x) (void)(x)

// use iid in an expression to get rid of warning, but have no observable effect
USE(iid); 

// hm, result of expression is gone but expression is still evaluated
USE(std::cout << "hmmm" << std::endl);

This is close to a solution:

// sizeof doesn't evaluate the expression
#define USE(x) (void)(sizeof(x))

But fails with:

void foo();

// oops, cannot take sizeof void
USE(foo());

The solution is to simply:

// use expression as sub-expression,
// then make type of full expression int, discard result
#define USE(x) (void)(sizeof((x), 0))

Which guarantees no operation.

Edit: The above indeed guaranteed no effect, but I posted without testing. Upon testing, it generates a warning again, at least in MSVC 2010, because the value isn't used. That's no good, time for more tricks!


Reminder: We want to "use" an expression without evaluating it. How can this be done? Like this:

#define USE(x) ((void)(true ? 0 : (x)))

This has a simple problem like last time (worse actually), in that (x) needs to be be convertible to int. This is, again, trivial to fix:

#define USE(x) ((void)(true ? 0 : ((x), 0)))

And we're back to same kind of effect we had last time (none), but this time x is "used" so we don't get any warnings. Done, right?

There is actually still one problem with this solution (and was present in the last un-solution as well, but went unnoticed), and it comes up in this example:

struct foo {};
void operator,(const foo&, int) {}

foo f;
USE(f); // oops, void isn't convertible to int!

That is, if the type of the expression (x) overloads the comma operator to something not convertible to int, the solution fails. Sure, unlikely, but for the sake of going completely overboard, we can fix it with:

#define USE(x) ((void)(true ? 0 : ((x), void(), 0)))

To make sure we really end up with zero. This trick brought to you by Johannes.


Also as noted, if the above wasn't enough, a stupid enough compiler could potentially "load" the expression 0 (into a register or something), then disregard it.

I think it's impossible to be rid of that, since we ultimately need an expression to result in a type of some sort to ignore, but if I ever think of it I'll add it.

Rectitude answered 27/10, 2010 at 7:50 Comment(17)
Though I agree, this doesn't mean the expression has to become a no-op. The compiler could, if it so desired, load the value into a register and then ignore it. While this would be silly it isn't unfathomable that an optimizing compiler leaves bits around sometimes.Kasiekask
@eda: It is required to do nothing. Please see the quote here.Rectitude
No, the expression "value" is discarded. Loading the value into a register, or otherwise indicating its existence to the CPU would not violate that rule. As far the language is concerned it had no discernable side-effects.Kasiekask
@edA: but it would be a waste of time to do so, and the compiler wouldn't merely have to avoid optimizing, it'd have to actively de-optimize the code.Francefrancene
I'm not saying a good optimizer would do this, nor do I know a case where it does, but I'm saying the author of the optimizer may have made a mistake -- it happens. So if it is absolutely critical that no code is generated, then it should not be present at all.Kasiekask
@edA: What of the last USE definition?Rectitude
Again, I don't see anything in the standard that would prevent 0 from being evaluated. But more importantly the (x) will be evaluated in this case, not what we want. To solve the original problem we should probably put the original parameter in a macro and attach the "unused" parameter (this works for GCC, other compilers must have an alternative)...Kasiekask
@edA: Huh? The whole thing is inside of sizeof, which does not evaluate its arguments. Granted, I guess it could conceivably load the constant result into a register and ignore it again.Rectitude
You are correct. sizeof should not evaluate the expression -- it resolves only into a constant time variable.Kasiekask
I get the warning "left operand of comma has no effect" from GCC. Any ideas?Bilateral
@Steven: Hm, nope. I wouldn't really try to use the macro, it was more of a how-far-is-too-far answer.Rectitude
Okay. I'm just using this: #define UNUSED(x) ((void)(sizeof(x)))Bilateral
@Steven Lu: Okay, but I think that warns on MSVC. It's better to not have the variable in the first place.Rectitude
@GMan I might use a conditional to check for MSVC then. I like to stick with gcc anyway. Thanks.Bilateral
This solution gives a warning in g++ 4.9.2.Pierro
The solution from 2017 gives warning: left operand of comma operator has no effect [-Wunused-value] on GCC 5.4.0, 5.5.0, and 9.2.1, warning: expression result unused [-Wunused-value] on XCode 11.3.1 and worked perfectly on MSVC 19.16.27038.0Metcalf
@Francefrancene I presume you've figured this out in the decade since that comment, but it does not require active de-optimization: the most simple compiler implementation just recurses down through expressions to the smallest expression it can generate machine code for, and generates the simplest machine code which combines to execute the specified behavior correctly (this is how you get GCC at no optimization spitting out code which just moves stuff to and from the stack repeatedly just to do a couple additions). In such a compiler, no active de-optimization is needed for (void)x to compile as x.Mainly
A
2

Well, it's not really possible to say with 100% certainty without looking at the compiler's source code, but I would be very surprised if this ever generated any code in modern compilers.

At the end of the day, if you're worried about any particular instance then you are always free to look at the generated assembly code.

Abernon answered 27/10, 2010 at 7:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.