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

C#,VS 2010

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

使用测试NaN的浮点数

float.IsNaN(aFloatNumber) 

堆栈溢出崩溃。

那样做

 aFloatNumber.CompareTo(float.NaN). 

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

 aFloatNumber - float.NaN 

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

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

编辑:调用堆栈:

在此处输入图像描述

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

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

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

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

编辑:

Debug.WriteLine()显示代码只执行一次:没有递归。

编辑:

这有效:

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

这崩溃了:

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

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

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

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

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

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

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

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

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

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

我刚刚编写了一个示例来重现错误: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); } 

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

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

正如MSDN所说:默认情况下,系统关闭了所有FPexception。 因此,计算导致NAN或INFINITY,而不是exception。

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

解决方案

首先,感谢@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()之前调用构造函数,但在InitCanUsbDLL之后调用事件处理程序很久( )损坏了FPU寄存器。