Unreachable code at constructor's closing brace
Asked Answered
A

4

7

I'm working on an application built with VC9 and I've hit upon a warning I don't fully understand: why is there an "unreachable code" warning on the closing brace of the constructor?

The minimal testcase to reproduce the issue is:

__declspec(noreturn) void foo() {
  // Do something, then terminate the program
}
struct A {
  A() {
    foo();
  } // d:\foo.cpp(7) : warning C4702: unreachable code
};
int main() {
  A a;
}

This must be compiled with /W4 to trigger the warning. Alternatively, you can compile with /we4702 to force an error on the detection of this warning.

d:\>cl /c /W4 foo.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

foo.cpp
d:\foo.cpp(7) : warning C4702: unreachable code

Can someone explain what, precisely, is unreachable here? My best theory is that it's the destructor, but I'd like a definitive answer.

If I want to make this code warning-clean, how can I achieve that? The best I can come up with is convert this to a compile-time error.

struct A {
private:
  A(); // No, you can't construct this!
};
int main() {
  A a;
}

Edit: for clarification, terminating the program with a noreturn function doesn't normally cause an unreachable code warning on the closing brace enclosing that function call.

__declspec(noreturn) void foo() {
  // Do something, then terminate the program
}
struct A {
  A() {
  }
  ~A() {
    foo();
  }
};
int main() {
  A a;
}

Results in:

d:\>cl /c /W4 foo3.cpp
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

foo3.cpp
Aggie answered 30/5, 2012 at 14:39 Comment(5)
Because you terminate the program in foo()?Khaki
Making something private and not implementing it is the usual way, C++11 has = delete to achieve that.Conventual
must foo() be __declspec(noreturn) ?Angwantibo
@ixe013: Yes, foo() is used elsewhere and it's important that we flag unreachable code after its use. I suppose we could have two versions of foo(), but that feels a little silly (a special version just for use in constructors?).Aggie
This looks like a compiler bug to me. So you are justified in disabling this warning for the duration of the constructor, with a big fat comment explaining why.Gathers
A
3

Gorpik is on the right track. I've created two similar test cases, compiled them, and disassembled them and I think I've come to understand the underlying reason: the constructor always generates a return statement implicitly and this return statement is unreachable due to the noreturn function.

noreturn_constructor.cpp

__declspec(noreturn) void foo() {
  // Do something, then terminate the program
}
struct A {
  A() {
    foo();
  }
  ~A() {
  }
};
int main() {
  A a;
}

noreturn_destructor.cpp

__declspec(noreturn) void foo() {
  // Do something, then terminate the program
}
struct A {
  A() {
  }
  ~A() {
    foo();
  }
};
int main() {
  A a;
}

diff -u *.disasm

--- noreturn_constructor.disasm 2012-05-30 11:15:02.000000000 -0400
+++ noreturn_destructor.disasm  2012-05-30 11:15:08.000000000 -0400
@@ -2,7 +2,7 @@
 Copyright (C) Microsoft Corporation.  All rights reserved.


-Dump of file noreturn_constructor.obj
+Dump of file noreturn_destructor.obj

 File Type: COFF OBJECT

@@ -35,15 +35,15 @@

 ??0A@@QEAA@XZ (public: __cdecl A::A(void)):
   0000000000000000: 48 89 4C 24 08     mov         qword ptr [rsp+8],rcx
-  0000000000000005: 48 83 EC 28        sub         rsp,28h
-  0000000000000009: E8 00 00 00 00     call        ?foo@@YAXXZ
-  000000000000000E: 48 8B 44 24 30     mov         rax,qword ptr [rsp+30h]
-  0000000000000013: 48 83 C4 28        add         rsp,28h
-  0000000000000017: C3                 ret
+  0000000000000005: 48 8B 44 24 08     mov         rax,qword ptr [rsp+8]
+  000000000000000A: C3                 ret

 ??1A@@QEAA@XZ (public: __cdecl A::~A(void)):
   0000000000000000: 48 89 4C 24 08     mov         qword ptr [rsp+8],rcx
-  0000000000000005: C3                 ret
+  0000000000000005: 48 83 EC 28        sub         rsp,28h
+  0000000000000009: E8 00 00 00 00     call        ?foo@@YAXXZ
+  000000000000000E: 48 83 C4 28        add         rsp,28h
+  0000000000000012: C3                 ret

   Summary

The unreachable code is this implicit return statement, which is generated in the constructor but not the destructor:

-  000000000000000E: 48 8B 44 24 30     mov         rax,qword ptr [rsp+30h]
+  0000000000000005: 48 8B 44 24 08     mov         rax,qword ptr [rsp+8]
Aggie answered 30/5, 2012 at 15:25 Comment(0)
G
2

There are no destructors to be called at the end of A::A(), so that's not the problem. What cannot be reached is the actual construction of the object, which happens after the constructor has finished its execution. Since it can never finish, that compiler-generated code is unreachable.

Gingersnap answered 30/5, 2012 at 14:54 Comment(3)
I think that's an interesting theory. Besides the user-specified body of the constructor, what code is generated to constitute the "actual construction of the object"?Aggie
What code is run after the user defined constructor? I mean clearly allocating memory, etc. has to be done before calling the user constructor, so what else is there?Gorges
@mrkj: I see you have investigated the issue, so I have nothing to add. +1 to your answer for the details and experimental confirmation, by the way.Gingersnap
P
1

The declspec(noreturn) on foo is producing this warning. You're telling the compiler that this function does not return. So the compiler is emitting a warning that your constructor will never complete.

Presently answered 30/5, 2012 at 14:45 Comment(1)
I may have been too vague in my original question, but I don't think this captures the essence of what I'm looking for in an answer: why does a constructor calling a noreturn function before terminating result in an unreachable code warning where calling the same noreturn function before terminating any other function does not result in an unreachable code warning?Aggie
D
1

see http://msdn.microsoft.com/en-us/library/k6ktzx3s(v=vs.80).aspx

"This __declspec attribute tells the compiler that a function does not return. As a consequence, the compiler knows that the code following a call to a __declspec(noreturn) function is unreachable."

The closing brace may generate code (like calling destructors), which will not be reached.

Dysprosium answered 30/5, 2012 at 14:45 Comment(2)
Could you be more specific about what generated code is unreachable in this case? The question was what, precisely, is unreachable (not "what does __declspec(noreturn) mean"). There should be no code generated for the destructor in this case; what would the destructor be doing? Why would it attribute the destructor's code to the constructor's closing brace?Aggie
Your are right, the explanation is too simpel. Looking at the disassembly of the code, the closing brace generates exact the same code for the constructor as for the destructor. So to my understanding the warning has to be generated in both cases (which I would not expect) or in none. BTW, a normal (or virtual) method also generates no warning.Dysprosium

© 2022 - 2024 — McMap. All rights reserved.