测试浮点数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进程中的027F
是027F
。 该值的最后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寄存器。