Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expected "warning: unused variable" but nothing is generated in C++

Test code:

struct X {
    X (int x) {}
};

int main() {
    X x1(0);     // nothing!
    X x2 = 0;    // warning: unused variable
    X x3 = X(0); // warning: unused variable
}

Question

Why no warning is generated for x1?

Notes:

  • I'm compiling with the -Wall option.

  • Both GCC and Clang produce equivalent output.

  • Those 3 lines generate the same assembler instructions (placed some asm("nop") for clarity):

    nop                      
    lea    -0x8(%rbp),%rdi   
    mov    $0x0,%esi         
    callq  400600 <X::X(int)>
    nop                      
    lea    -0x10(%rbp),%rdi  
    mov    $0x0,%esi         
    callq  400600 <X::X(int)>
    nop                      
    lea    -0x18(%rbp),%rdi  
    mov    $0x0,%esi         
    callq  400600 <X::X(int)>
    nop
    
  • It does not happen with primitive types (e.g., typedef int X).

  • If the reason concerns the fact that there may be side effects in the constructor then the question becomes: why do I get the other two warnings?


Edit:

  • This is not a duplicate of this question IMO since in this case the constructor is non-trivial.
like image 284
cYrus Avatar asked Feb 07 '17 20:02

cYrus


2 Answers

The first one calls your user-defined constructor to perform direct initialization. This could have side-effects. Hence x1 is used. (Or at least it cannot easily enough be determined to be unused.)

The second one copy constructs. It calls your user-defined constructor to create a temporary. It then calls the default copy constructor to construct x2. (See below in edit for more detailed discussion and refinement.) Hence x2 can be determined to be unused because it was created by a compiler-generated constructor with no side-effects, and x2 itself does not appear anywhere else.

The third is like the second. A temporary is created via your user-provided constructor, and then copied. The temporary is used, but x3 itself is not.

See the accepted answer to this question: Is there a difference in C++ between copy initialization and direct initialization?

EDIT

Based on the comments, here's some further discussion.

First, there was the comment that the warning is misleading. This is perhaps somewhat subjective, but I would point out that warnings of this sort are often provided on a "best effort" basis only. It could be that something that is not guaranteed in general is true in a specific case if the compiler would only dig far enough into the code to check. At some point, though, the compiler developers have to draw a line. That means that you generally cannot count on a warning like this to catch every case of an unused variable. (On the other hand, if you have a variable that is used and you get a warning, that would be a bug.) My personal feeling is that the behavior demonstrated here is not misleading, but, again, I see that there's some personal interpretation in making such a call.

Second, it was pointed out by @FrançoisAndrieux that if you modified the example given by the OP to include a user-defined copy constructor, you still get the same warnings. That calls into question my explanation above, which referenced the default copy constructor. This touches on a second point of theory, which I'll follow up with a specific answer for this case. The point of theory is that, unless you really want to dig into the compiler itself and get its specific rules, the issue at hand is whether the compiler can reasonably know that the variable is unused, keeping in mind that there may be more than one way that the compiler could have reached its conclusion.

As the example was originally posted, I think my answer gives a way that the complier could have come to its conclusion. Another way, that seems to be applicable both to the original form and to the modified form suggested by the comment is based on copy elision. There's an extensive discussion of this here: What are copy elision and return value optimization? The key point for the current discussion is that, specifically for copy constructors, the compiler is allowed to ignore side effects. In fact, in some cases, the compiler is required to ignore the copy. Evidently, the compiler here is taking this into account, either directly or indirectly, when issuing the warning. This also probably goes to the point made by the OP that the assembly code in all three cases is the same - that's because the copy action has been optimized out.

Let me try now to anticipate the next question: "If the copy construct is elided, why aren't the three cases really the same?" The answer here, I think, is in which specific variable are unused. It's the unnamed, temp variables in the second and third case that are constructed, not the named variables x2 and x3. The temp variables fall into same pattern as the first case in the example and are "used" (or at least cannot be determined easily enough to be unused). That still leaves x2 and x3 unused by any standard once the copy construct is optimized out.

like image 87
Brick Avatar answered Sep 24 '22 00:09

Brick


@Brick answer's follows. For different semantics you can end up with same final object code, to prove that try add -fno-elide-constructors to your flags, elision is the culprit that leads to same object code, even in -O0.

like image 26
pepper_chico Avatar answered Sep 24 '22 00:09

pepper_chico