Тестирование для float NaN приводит к переполнению стека

С#, VS 2010

Мне нужно определить, является ли значение float 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
  • имеет обработчик события Validate ValidateTextBoxEntry
  • ValidateTextBoxEntry определяется внутри класса ConfigNumberBaseBox
  • Пользовательский элемент управления, который наследует от ConfigNumberBaseBox: открытый частичный класс ConfigTemperBox: ConfigNumberBaseBox
  • Запустите приложение
  • Когда я закончу редактирование элемента управления ConfigTemperBox, ValidateTextBoxEntry вызывается
  • ValidateTextBoxEntry работает нормально, пока не встретит float.IsNaN
  • переполнение стека

Edit:

Debug.WriteLine() показывает, что код выполняется только один раз: нет рекурсии.

Edit:

Это работает:

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

Сбой:

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

Ответы

Ответ 1

Решение:

Прежде всего, спасибо @Matt за то, что указали мне в правильном направлении, и @Hans Passant для обеспечения обходного пути.

Приложение обращается к адаптеру CAN-USB от китайского производителя QM_CAN.

Проблема в драйвере.

Операторы 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);

Призыв к нарушившемуся коду, включая обход Ханса:

   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 Qaru 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 Qaru 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.

Ответ 2

работает ОК в конструкторе класса для настраиваемого элемента управления

Это единственный реальный намек на лежащую в основе проблему. Код, который работает в потоке, может управлять двумя пакетами внутри процессора. Один из них является нормальным, о котором все знают, и дал этому сайту название. Существует, однако, еще один, хорошо скрытый внутри FPU (модуль с плавающей точкой). Он хранит значения промежуточных операндов при вычислении с плавающей запятой. Это 8 уровней.

Любой вид сбоя внутри FPU не должен генерировать исключения во время выполнения. CLR предполагает, что FPU сконфигурирован со своими значениями по умолчанию для управляющего слова FPU, предполагается, что исключенные аппаратные исключения могут быть отключены.

У этого есть умение идти неправильно, когда ваша программа использует код, который пришел с 1990-х годов, назад, когда включение исключений FPU по-прежнему звучало как хорошая идея. Код, созданный инструментами Borland, известен тем, что делает это, например. Его модуль времени выполнения C перепрограммирует управляющее слово FPU и устраняет аппаратные исключения. Тип исключения, который вы можете получить для этого, может быть очень загадочным, использование NaN в коде - хороший способ вызвать такое исключение.

Это должно быть, по крайней мере, частично видимым с помощью отладчика. Установите точку останова на "все еще хороший" код и используйте окно отладки Debug + Windows + Registers. Щелкните его правой кнопкой мыши и выберите "Плавающая точка". Вы увидите все регистры, которые участвуют в вычислениях с плавающей запятой, ST0-ST7 - это, например, регистры стека. Здесь важно отметить CTRL, его нормальное значение в .NET-процессе - 027F. Последние 6 бит в этом значении являются битами маскировки исключения (0x3F), все включены для предотвращения аппаратных исключений.

Один шаг через код, и ожидается, что вы увидите изменение значения CTRL. Как только это произойдет, вы найдете злой код. Если вы включаете неуправляемую отладку, вы также должны увидеть уведомление о загрузке в окне "Выход" и увидеть его в окне "Отладка + Windows +".

Отменить повреждение, которое сделала DLL, довольно неудобно. Вам нужно будет pinvoke _control87() в msvcrt.dll, например, чтобы восстановить слово CTRL. Или простой трюк, который вы можете использовать, вы можете преднамеренно выбросить исключение. Логика обработки исключений внутри CLR сбрасывает управляющее слово FPU. Поэтому, с некоторой удачей, такой код решит вашу проблему:

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

Возможно, вам придется переместить его, а следующая оценка - событие Load. Отладчик должен сообщить вам, где.

Ответ 3

Я просто написал пример, чтобы воспроизвести ошибку: 1. Создайте собственную 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. Создайте консольную программу С#, которая после этого вызовет функцию 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, предполагается, что прикладному программному обеспечению больше не нужно обрабатывать исключения с плавающей запятой.