【问题标题】:Unreachable code at constructor's closing brace在构造函数的右括号处无法访问代码
【发布时间】:2012-05-30 14:39:43
【问题描述】:

我正在开发一个使用 VC9 构建的应用程序,但我遇到了一个我不完全理解的警告:为什么在构造函数的右大括号上有一个“无法访问代码”警告?强>

重现问题的最小测试用例是:

__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;
}

这必须用 /W4 编译才能触发警告。或者,您可以使用 /we4702 进行编译,以在检测到此警告时强制出错。

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

有人能准确地解释一下这里无法到达的地方吗?我最好的理论是它是析构函数,但我想要一个明确的答案。

如果我想让这段代码警告干净,我该如何实现?我能想到的最好的办法就是将它转换为编译时错误。

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

编辑:为澄清起见,使用 noreturn 函数终止程序通常不会在包含该函数调用的右大括号上引起无法访问的代码警告。

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

结果:

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

【问题讨论】:

  • 因为你终止了foo()中的程序?
  • 制作 private 而不实现它是通常的方式,C++11= delete 来实现它。
  • 必须 foo()__declspec(noreturn) 吗?
  • @ixe013:是的,foo() 在其他地方使用,重要的是我们在使用后标记无法访问的代码。我想我们可以有两个版本的foo(),但这感觉有点傻(一个特殊的版本只用于构造函数?)。
  • 这在我看来像是一个编译器错误。因此,您有理由在构造函数期间禁用此警告,并用一个大注释解释原因。

标签: c++ visual-c++ constructor visual-c++-2008 unreachable-code


【解决方案1】:

Gorpik 走在正确的轨道上。我已经创建了两个类似的测试用例,编译并反汇编了它们,我想我已经理解了根本原因:构造函数总是隐式地生成一个 return 语句,而这个 return 语句由于 noreturn 函数而无法访问。

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

无法到达的代码是这个隐式的return语句,它是在构造函数而不是析构函数中生成的:

-  000000000000000E: 48 8B 44 24 30     mov         rax,qword ptr [rsp+30h]
+  0000000000000005: 48 8B 44 24 08     mov         rax,qword ptr [rsp+8]

【讨论】:

    【解决方案2】:

    A::A() 的末尾没有要调用的析构函数,所以这不是问题。无法达到的是对象的实际构造,它发生在构造函数完成执行之后。由于它永远无法完成,因此编译器生成的代码无法访问。

    【讨论】:

    • 我认为这是一个有趣的理论。除了用户指定的构造器主体外,还生成了哪些代码来构成“对象的实际构造”?
    • 在用户定义的构造函数之后运行什么代码?我的意思是明确分配内存等必须在调用用户构造函数之前完成,那么还有什么?
    • @mrkj:我看到你已经调查了这个问题,所以我没有什么要补充的。顺便说一下,+1 以获取详细信息和实验确认的答案。
    【解决方案3】:

    foo 上的 declspec(noreturn) 产生了这个警告。你告诉编译器这个函数不会返回。所以编译器会发出一个警告,你的构造函数永远不会完成。

    【讨论】:

    • 我最初的问题可能过于模糊,但我认为这并没有抓住我在答案中寻找的本质:为什么构造函数在终止之前调用 noreturn 函数导致无法访问代码警告如果在终止任何其他函数之前调用相同的 noreturn 函数 not 会导致无法访问代码警告吗?
    【解决方案4】:

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

    “这个 __declspec 属性告诉编译器一个函数没有返回。因此,编译器知道调用 __declspec(noreturn) 函数之后的代码是不可达的。”

    右大括号可能会生成代码(如调用析构函数),无法到达。

    【讨论】:

    • 您能否更具体地说明在这种情况下哪些生成的代码无法访问?问题是确切地说是什么是无法访问的(不是“__declspec(noreturn) 是什么意思”)。在这种情况下,不应该为析构函数生成代码;析构函数会做什么?为什么要将析构函数的代码归于构造函数的右大括号?
    • 你说得对,解释太简单了。查看代码的反汇编,右大括号为构造函数和析构函数生成完全相同的代码。因此,据我了解,必须在两种情况下(我不希望)或不生成警告。顺便说一句,普通(或虚拟)方法也不会产生警告。
    猜你喜欢
    • 1970-01-01
    • 2018-09-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多