测试浮点数NaN会导致堆栈溢出

时间:2014-08-08 13:52:20

标签: c# stack-overflow nan

C#,VS 2010

我需要确定浮点值是否为NaN。

使用

测试NaN的浮点数
float.IsNaN(aFloatNumber) 

堆栈溢出崩溃。

也是如此

aFloatNumber.CompareTo(float.NaN).

以下内容不会崩溃,但它无用,因为无论如何都会返回NaN:

aFloatNumber - float.NaN 

搜索"堆栈溢出"返回有关此网站的结果,而不是实际堆栈溢出的结果,因此我无法找到相关答案。

为什么我的应用程序在测试NaN时会进入堆栈溢出?

编辑:调用堆栈:

enter image description here

编辑:我的代码中显然有些东西:这句话:

bool aaa = float.IsNaN(float.NaN);
  • 在InitializeComponent();
  • 之后立即在应用程序的构造函数中正常工作
  • 在InitializeComponent()之后立即在自定义控件的类的构造函数中正常工作;
  • 但在类中的事件处理程序中崩溃以进行自定义控件。

所以,这就是我在做的事情:

  • 抽象自定义控件:公共抽象部分类ConfigNumberBaseBox:TextBox
  • 有一个Validating事件处理程序ValidateTextBoxEntry
  • ValidateTextBoxEntry在ConfigNumberBaseBox类
  • 中定义
  • 继承自ConfigNumberBaseBox的自定义控件:public partial class ConfigTemperBox:ConfigNumberBaseBox
  • 运行应用
  • 当我完成编辑ConfigTemperBox控件时,调用ValidateTextBoxEntry
  • ValidateTextBoxEntry运行正常,直到遇到float.IsNaN
  • 堆栈溢出

编辑:

Debug.WriteLine()显示代码只执行一次:no recursion。

编辑:

这有效:

float fff = 0F;
int iii = fff.CompareTo(float.PositiveInfinity);

崩溃了:

float fff = 0F;
int iii = fff.CompareTo(float.NaN);

3 个答案:

答案 0 :(得分:25)

  

在自定义控件的类的构造函数中正常工作

这是对潜在问题的唯一真实暗示。在线程上运行的代码可以操作处理器内的两个堆栈。一个是每个人都知道的正常的,并给这个网站起了名字。然而,还有另一个,隐藏在FPU(浮点单元)内部。它在进行浮点计算时存储中间操作数值。这是8级深。

FPU中的任何类型的不幸事件都是而不是应该生成运行时异常。 CLR假设FPU配置了FPU控制字的默认值,它可以生成的硬件异常应被禁用。

当您的程序使用来自20世纪90年代的代码时,确实有一个出错的诀窍,当时启用FPU异常仍然听起来像个好主意。例如,Borland工具生成的代码因此而臭名昭着。其C运行时模块重新编程 FPU控制字并取消屏蔽硬件异常。您可以获得的异常类型可能非常神秘,在代码中使用NaN是触发此类异常的好方法。

这应该至少部分可见于调试器。在“仍然良好”的代码上设置断点并使用Debug + Windows + Registers调试器窗口。右键单击它并选择“浮点”。您将看到所有与浮点计算有关的寄存器,例如ST0到ST7是堆栈寄存器。这里重要的一个标记为CTRL,它在.NET进程中的正常值是027F。该值的最后6位是异常屏蔽位(0x3F),全部打开以防止硬件异常。

单步执行代码,期望您看到CTRL值更改。一旦它发生,你就会找到邪恶的代码。如果启用非托管调试,那么您还应该在“输出”窗口中看到加载通知,并看到它出现在“调试+ Windows +模块”窗口中。

撤消DLL所造成的损害是相当尴尬的。你必须在msvcrt.dll中修改_control87(),例如恢复CTRL字。或者您可以使用的简单技巧,您可以故意抛出异常。 CLR内部的异常处理逻辑重置FPU控制字。幸运的是,这种代码可以解决您的问题:

    InitializeComponent();
    try { throw new Exception("Please ignore, resetting FPU"); }
    catch {}

您可能需要移动它,接下来最好的猜测是Load事件。调试器应该告诉你在哪里。

答案 1 :(得分:6)

我刚刚写了一个例子来重现错误: 1.创建导出此函数的本机C / C ++ DLL:

extern "C" __declspec(dllexport)  int SetfloatingControlWord(void)
{
    //unmask all the floating excetpions
    int err = _controlfp_s(NULL, 0, _MCW_EM);    
    return err;
}

2。创建一个C#控制台程序,调用函数SetfloatingControlWord,然后执行一些浮动操作,如NaN比较,然后导致堆栈溢出。

 [DllImport("floatcontrol.dll")]
        public static extern Int32 SetfloatingControlWord();       
        static void Main(string[] args)
        {
           int n =   SetfloatingControlWord();          
           float fff = 0F;
           int iii = fff.CompareTo(float.NaN);
        }

我在几年前遇到了同样的问题,同样,我注意到在.NET异常抛出后,一切正常,我花了一段时间找出原因并跟踪改变FPU的代码。

正如函数文档_controlfp_s所说:默认情况下,运行时库会屏蔽所有浮点异常。公共语言运行库(CLR)仅支持默认的浮点精度,因此CLR不处理这些类型的异常。

正如MSDN所述:默认情况下,系统已关闭所有FP异常。因此,计算导致NAN或INFINITY,而不是异常。

NaN在IEEE 754 1985中引入后,假设应用软件不再需要处理浮点异常。

答案 2 :(得分:2)

解决方案

首先,感谢@Matt指出我正确的方向,感谢@Hans Passant提供解决方法。

该应用程序与中国制造商QM_CAN的CAN-USB适配器进行通信。

问题出在他们的司机身上。

DLL语句和驱动程序导入:

   // DLL Statement
    IntPtr QM_DLL;
    TYPE_Init_can Init_can;
    TYPE_Quit_can Quit_can;
    TYPE_Can_send Can_send;
    TYPE_Can_receive Can_receive;
    delegate int TYPE_Init_can(byte com_NUM, byte Model, int CanBaudRate, byte SET_ID_TYPE, byte FILTER_MODE, byte[] RXF, byte[] RXM);
    delegate int TYPE_Quit_can();
    delegate int TYPE_Can_send(byte[] IDbuff, byte[] Databuff, byte FreamType, byte Bytes);
    delegate int TYPE_Can_receive(byte[] IDbuff, byte[] Databuff, byte[] FreamType, byte[] Bytes);

    // Driver
    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string lpFileName);
    [DllImport("kernel32.dll")]
    static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

对违规代码的调用,包括Hans的解决方法:

   private void InitCanUsbDLL() // Initiate the driver for the CAN-USB dongle
    {

        // Here is an example of dynamically loaded DLL functions
        QM_DLL = LoadLibrary("QM_USB.dll");
        if (QM_DLL != IntPtr.Zero)
        {
            IntPtr P_Init_can = GetProcAddress(QM_DLL, "Init_can");
            IntPtr P_Quit_can = GetProcAddress(QM_DLL, "Quit_can");
            IntPtr P_Can_send = GetProcAddress(QM_DLL, "Can_send");
            IntPtr P_Can_receive = GetProcAddress(QM_DLL, "Can_receive");
            // The next line results in a FPU stack overflow if float.NaN is called by a handler
            Init_can = (TYPE_Init_can)Marshal.GetDelegateForFunctionPointer(P_Init_can, typeof(TYPE_Init_can));
            // Reset the FPU, otherwise we get a stack overflow when we work with float.NaN within a event handler
            // Thanks to Matt for pointing me in the right direction and to Hans Passant for this workaround:
            // http://stackoverflow.com/questions/25205112/testing-for-a-float-nan-results-in-a-stack-overflow/25206025
            try { throw new Exception("Please ignore, resetting FPU"); }
            catch { } 
            Quit_can = (TYPE_Quit_can)Marshal.GetDelegateForFunctionPointer(P_Quit_can, typeof(TYPE_Quit_can));
            Can_send = (TYPE_Can_send)Marshal.GetDelegateForFunctionPointer(P_Can_send, typeof(TYPE_Can_send));
            Can_receive = (TYPE_Can_receive)Marshal.GetDelegateForFunctionPointer(P_Can_receive, typeof(TYPE_Can_receive));
        }
    }

当在事件处理程序中而不是在构造函数中引用float.NaN时,应用程序崩溃的原因很简单:在InitCanUsbDLL()之前调用构造函数,但事件处理程序被调用为long在InitCanUsbDLL()损坏了FPU寄存器之后。