c++ 在VS2012中使用sqrt内在的堆栈运行时检查失败

在调试一些崩溃时,我遇到了一些代码,简化了以下情况:

#include <cmath>
#pragma intrinsic (sqrt) 

class MyClass
{
public:
  MyClass() { m[0] = 0; }
  double& x() { return m[0]; }
private:
  double m[1];
};
void function()
{
  MyClass obj;
  obj.x() = -sqrt(2.0);
}

int main()
{
  function();
  return 0;
}

当内置于Debug | Win32 with VS2012(Pro版本11.0.61030.00 Update 4和Express for Windows Desktop版本11.0.61030.00 Update 4)时,代码会在函数执行结束时触发运行时检查错误,显示为(以随机方式):

Run-Time Check Failure #2 – Stack around the variable ‘obj’ was corrupted.

要么

A buffer overrun has occurred in Test.exe which has corrupted the program’s internal state. Press Break to debug the program or Continue to terminate the program.

我明白这通常意味着堆栈上的对象的某种缓冲区溢出/欠载.也许我忽略了一些东西,但是我看不到这个C代码中可能发生这种缓冲区溢出的任何地方.在对代码进行各种调整后,通过生成的函数汇编代码(见下面的“详细信息”部分),我会很乐意在Visual Studio 2012中看起来像一个错误,但也许我是只是在太深而且遗漏的东西.

是否有内在功能使用要求或此代码不符合的其他C标准要求,这可以解释这种行为?

如果不是,禁用功能内在的唯一方法是获得正确的运行时检查行为(除了解决方法,如下面提到的0-sqrt可能很容易丢失)?

细节

在编写代码的过程中,我注意到,通过注释掉#pragma行来禁用sqrt内在的运行时检查错误.

否则使用sqrt内在的pragma(或/ Oi编译器选项):

>使用一个设置器,如obj.setx(double x){m [0] = x; },毫不奇怪也会产生运行时检查错误.
>将obj.x()= -sqrt(2.0)与obj.x()= sqrt(2.0)或obj.x()= 0.0-sqrt(2.0))替换为我的惊喜不会生成运行时检查错误.
>同样用obj.x()= -1.4142135623730951替换obj.x()= -sqrt(2.0);不生成运行时检查错误.
>更换会员双m [1];双米; (连同m [0]访问)似乎只会生成“运行时检查失败#2”错误(即使使用obj.x()= -sqrt(2.0)),有时运行正常.
>将obj声明为静态实例,或者在堆上分配它不会生成运行时检查错误.
>将编译器警告设置为4级不会产生任何警告.
>使用VS2005 Pro或VS2010 Express编译相同的代码不会生成运行时检查错误.
对于什么是值得的,我已经注意到Windows 7(使用Intel Xeon CPU)和Windows 8.1机器(使用Intel Core i7 CPU)的问题.

然后我继续查看生成的汇编代码.为了说明的目的,我将参考“失败的版本”作为从上面提供的代码获得的,而我通过简单地注释#pragma内在(sqrt)行生成了一个“工作版本”.下面显示了生成的汇编代码的并行差异视图,左侧的“失败版本”和右侧的“工作版本”

首先,我注意到_RTC_CheckStackVars调用负责“运行时检查失败#2”错误,特别是当魔术曲奇饼0xCCCCCCCC在堆栈上的obj对象周围仍然完整(这恰好从一个相对于ESP原始值的-20字节的偏移量).在下面的截图中,我突出显示了绿色的对象位置和红色的魔术cookie位置.在“工作版”功能的开始,这是它的样子:

那么在调用_RTC_CheckStackVars之前呢?

现在在“失败的版本”中,序言包括一个附加的(行3415)

and         esp,0FFFFFFF8h

这基本上使对象在8字节边界上对齐.具体来说,每当使用以0或8半字节结尾的ESP的初始值调用函数时,obj就以相对于ESP的初始值的-24个字节的偏移开始存储.
问题是,_RTC_CheckStackVars仍然在与上述“工作版本”(即,-24和-12个字节的偏移量)中相对于原始ESP值在相同位置处查找这些0xCCCCCCCC魔术cookie.在这种情况下,obj的前4个字节实际上重叠了一个魔术cookie位置.在“失败版本”的开头显示在下面的截图中:

那么在调用_RTC_CheckStackVars之前呢?

我们可以通过在“工作版本”和“失败版本”(“cd 3b 7f 66 9e a0 f6 bf”“中相当于obj.m [0]的实际数据相同,或者 – 1.4142135623730951当解释为双).

顺便提一句,只要ESP的初始值以4或C半字节结束(在这种情况下,obj从-20字节偏移开始,就像在“工作版本”中),则_RTC_CheckStackVars检查实际上是通过的.

在_RTC_CheckStackVars检查完成(假设通过)之后,还有一个附加检查,ESP的恢复值对应于原始值.此检查失败时,负责“A缓冲区溢出发生在…”消息中.

在“工作版本”中,原始ESP在序言(第3415行)早期被复制到EBP,它是用于通过使用___security_cookie(第3425行)计算校验和的值.在“故障版本”中,校验和计算基于ESP(第3425行),ESP在推送某些寄存器(第3417-3419行)时递减12,但是与恢复的ESP相对应的检查在同一点完成那些寄存器已被恢复.

所以简而言之,除非我没有得到这样的权利,它看起来像“工作版本”遵循标准的教科书和教程的堆栈处理,而“失败的版本”混乱的运行时检查.

P.S .:“Debug build”是指来自“Win32 Console Application”新项目模板的“Debug”配置的编译器选项的标准集合.

正如汉斯在评论中指出的那样,Visual Studio 2013不能再复制这个问题.
同样地,Microsoft connect bug report的官方回答是:

we are unable to reproduce it with VS2013 Update 4 RTM. The product team itself no longer directly accepting feedback for Microsoft Visual Studio 2012 and earlier products. You can get support for issues with Visual Studio 2012 and earlier by visiting one of the resources in the link below:
07001

因此,考虑到只有在具有函数内在函数(/ Oi编译器选项)的VS2012上才会触发问题,运行时检查(/ RTCs或/ RTC1编译器选项)和一元减号运算符的使用,摆脱任何一个(或更多)在这些情况下应该解决问题.

因此,似乎可用的选项是:

>升级到最新的Visual Studio(如果您的项目允许)
>通过使用#pragma runtime_check(例如以下示例)来围绕受影响的函数来禁用运行时检查:

    #pragma runtime_check ("s", off)
    void function()
    {
      MyClass obj;
      obj.x() = -sqrt(2.0);
    }
    #pragma runtime_check ("s", restore)

通过删除#pragma内在函数(sqrt)行来禁用内在函数,并添加#pragma函数(sqrt)(有关更多信息,请参阅msdn).
如果通过“启用内在函数”项目属性(/ Oi编译器选项)激活所有文件的内在函数,则需要停用该项目属性.然后,您可以逐个启动内在函数,以查看特定功能,同时检查它们是否不受该错误的影响(每个必需的内在函数使用#pragma intrinsics指令).
>使用0-sqrt(2.0),-1 * sqrt(2.0)(删除一元减号运算符)等变通方法来调整代码,以试图使编译器使用不同的代码生成路径.请注意,这很可能会因为看似轻微的代码更改而崩溃.

翻译自:https://stackoverflow.com/questions/27987391/stack-runtime-check-failure-with-sqrt-intrinsic-in-vs2012

转载注明原文:c++ 在VS2012中使用sqrt内在的堆栈运行时检查失败