Q:

Коварная ошибка в коде или баг компилятора MinGW g++?

Я тут новенький на сайте, по-этому очень прошу не удалять мой пост сразу, как это делают в англоязычном сегменте. А где ещё искать решения моей проблемы? Я сам испробовал все доступные мне пути отладки. 
В моей программе имеется класс, и в нём метод. Так вот, если этот метод объявлен как __attribute__((noinline)), т.е. принудительно компилируется как обычная не встраиваемая функция, то всё работает как надо.
А вот без этого атрибута компилятор делает его по умолчанию встраиваемым (inline) и делает это похоже некорректно, т.к. приложение в определённый момент виснет. 
Также всё вдруг начинает хорошо работать, если в месте вызова этого метода вставить вызов какой-нибудь ничего не значащей функции, наподобие printf() или Beep(). 
Подскажите пожалуйста, в чём может быть проблема? Если это я где-то напортачил, то не могу понять где. Но пока это очень похоже на баг самого компилятора, который неверно оптимизирует inline-функцию (случай редчайший и маловероятный, но всё же).
Использую пакет MinGW 8.2.0 2018-го года, ранее с ним проблем никогда не было.
Исходный код вместе с бинарником и bat-файлом для компиляции прилагаю тут.
Учитывая рекомендации в ответах, привожу исходный код целиком непосредственно здесь:
/* 
 * mingw_bug.cpp
 *
 * This program demonstrates the serious MinGW g++ compiler optimization BUG!
 * The ISSUE: compiler does incorrectly inlining of the given method (lines 257-275).
 * See the lines 131..147 and 257-275 for detailes!

 * Version of the compiler: 8.2.0 (year 2018)
 * Compilation command line:
 *   C:\MinGW\bin\g++ -c -fno-rtti -O2 -march=native mingw_bug.cpp      
 *   C:\MinGW\bin\g++.exe -static-libstdc++ -o mingw_bug.exe mingw_bug.o
 * Found by Kondryukov D.V.
*/

#include <windows.h>             
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>

#ifdef __GNUC__
#define INLINE __attribute__ ((always_inline)) inline
#else
#define INLINE inline
#endif //__GNUC__

#ifdef __GNUC__
#define NOINLINE __attribute__ ((noinline))
#else
#define NOINLINE inline
#endif //__GNUC__

#define TIME_INTERVAL       1.0

#define INPUT_POOL_SIZE     8
#define INPUT_KEYDOWN       0x00001000
#define INPUT_KEYUP         0x00002000

class CInput
{
    struct EVENT_RECORD
    {
        EVENT_RECORD*   pNext;
        UINT            uEvent;
    };

    HANDLE          m_hIn;
    EVENT_RECORD    m_uEventPool[INPUT_POOL_SIZE];
    EVENT_RECORD*   m_pFreeEventRec;
    EVENT_RECORD*   m_pFirstEventRec;

public:
    CInput();

    void Update();
    bool CheckEvent(UINT uEvent);
    void FlushEvents();
};

#define INITIAL_FPS     30.0
#define MAX_TIME_DELTA  0.05

class CTimer
{
    LARGE_INTEGER m_liStartCounter;
    LARGE_INTEGER m_liCurrentCounter;
    double      m_lfInvFreq;
    double      m_lfTime;
    float       m_fTimeDelta;
    bool        m_bPaused;

public:
    CTimer();
    void Update();
    void TogglePause();
    INLINE double GetTime()     {return m_lfTime;}
    INLINE float GetTimeDelta() {return m_fTimeDelta;}
    INLINE bool IsPaused()      {return m_bPaused;}
};

#define MAX_COUNTER 1

class CTest
{
    UINT    m_uCounter;

public:
    CTest();

    void ToggleCounter();
};

void ExitFatal(UINT uErrorCode, const char* pszMessage);

CInput          g_In;
CTimer          g_Timer;
CTest           g_Test;

int main()
{
    double fNextTime = g_Timer.GetTime()+TIME_INTERVAL;
    printf(
        "This application demonstrates the MinGW g++ compiler optimization bug!\n"
        "ESC - exit; SPACE - toggle timer pause; TAB - the BUG demonstration...\n");

    while (true)
    {
        //update timer and input state
        g_Timer.Update();
        g_In.Update();

        //check if ESCAPE was pressed for exit
        if (g_In.CheckEvent(VK_ESCAPE|INPUT_KEYDOWN))
        {
            printf("<exiting...>>\n");
            break;
        }

        //check if SPACE was pressed to toggle pause
        if (g_In.CheckEvent(VK_SPACE|INPUT_KEYDOWN))
        {
            g_Timer.TogglePause();
            printf("\n<toggle timer pause>\n");
        }

        /* The code causing compiler bug!
         * If method CInput::FlushEvents() is forced to be not inline evrything works good!
         * Also, If line 142 is uncommented evrything works good well! 
         * HOWEVER! If method CInput::FlushEvents() is INLINE (by default)... 
         * ...and line 142 is commented, the APPLICATION will HUNG when you will press TAB!!!
         */
        if (g_In.CheckEvent(VK_TAB|INPUT_KEYDOWN))
        {
            g_Test.ToggleCounter();

            //uncomment following line for bug hiding...
            //printf("\nPress TAB again, and again... Ups!.. Where is the bug? Nothing hungs... o:)))\n"); //line 142
        }   

        //BUG is here! The application will hung!
        //But uncomment line 142 and the bug will hide!
        g_In.FlushEvents();

        if (g_Timer.GetTime()>=fNextTime)
        {
            printf("*");
            fNextTime+= TIME_INTERVAL;
        }
    }

    return 0;
}

void ExitFatal(UINT uErrorCode, const char* pszMessage)
{
    printf("FATAL ERROR #%4.0X (%s), terminating!\n\n\n",uErrorCode,pszMessage);
    Beep(1500,300);
    Sleep(6000);
    exit(1);
}

CInput::CInput()
{
    m_hIn = GetStdHandle(STD_INPUT_HANDLE);
    if (m_hIn==INVALID_HANDLE_VALUE) ExitFatal(0x0000,"input initialization");
    if (!FlushConsoleInputBuffer(m_hIn)) ExitFatal(0x0001,"input initialization");

    EVENT_RECORD* pLastRec = m_uEventPool+INPUT_POOL_SIZE-1;
    EVENT_RECORD* pRec = m_uEventPool;
    m_pFreeEventRec = pRec;
    m_pFirstEventRec = NULL;

    while(pRec<pLastRec)
    {
        pRec->pNext = pRec+1;
        ++pRec;
    }

    pRec->pNext = NULL;
}

void CInput::Update()
{
    INPUT_RECORD ir[INPUT_POOL_SIZE];
    DWORD nEvents;

    while (true)
    {
        if (!PeekConsoleInput(m_hIn,ir,INPUT_POOL_SIZE,&nEvents)) ExitFatal(0x0002,"input peeking");
        if (!nEvents) return;
        if (!ReadConsoleInput(m_hIn,ir,nEvents,&nEvents)) ExitFatal(0x0003,"input reading");

        INPUT_RECORD* p_ir_end = ir+nEvents;
        for (INPUT_RECORD* p_ir = ir; p_ir<p_ir_end; ++p_ir)
        {
            switch (p_ir->EventType)
            {
            case KEY_EVENT:
                if (m_pFreeEventRec)
                {
                    EVENT_RECORD* pRec = m_pFreeEventRec;
                    m_pFreeEventRec = pRec->pNext;
                    pRec->pNext = m_pFirstEventRec;
                    m_pFirstEventRec = pRec;

                    pRec->uEvent = (p_ir->Event.KeyEvent.wVirtualKeyCode)&0xFFF;
                    if (p_ir->Event.KeyEvent.bKeyDown) pRec->uEvent|= INPUT_KEYDOWN;
                    else pRec->uEvent|= INPUT_KEYUP;
                    continue; 
                }
                goto Flush;
            }
        }
    }

    Flush:
    Beep(896,200);
    if (!FlushConsoleInputBuffer(m_hIn)) ExitFatal(0x0004, "flushing input");

    //force free the last event to unlock subsequent input
    m_pFreeEventRec = m_pFirstEventRec;
    m_pFirstEventRec = m_pFirstEventRec->pNext;
    m_pFreeEventRec->pNext = NULL;
}

bool CInput::CheckEvent(UINT uEvent)
{
    EVENT_RECORD* pCur = m_pFirstEventRec;
    EVENT_RECORD* pPrev = (EVENT_RECORD*)&m_pFirstEventRec;

    while (pCur)
    {
        if (pCur->uEvent==uEvent)
        {
            pPrev->pNext = pCur->pNext;
            pCur->pNext = m_pFreeEventRec;
            m_pFreeEventRec = pCur;
            return true;
        }
        pPrev = pCur;
        pCur = pCur->pNext;
    }

    return false;
}

/* 
 * This method cause to be the application hung if it is INLINE or line 142 is commented!
 * It works correctly if it forced to be NOT INLINE or line 142 is uncommented
 * MinGW g++ does it's optimization uncorrectly!!!
 */
NOINLINE void CInput::FlushEvents()
{
    EVENT_RECORD* pCur = m_pFirstEventRec;

    while (pCur)
    {
        EVENT_RECORD* pNext = pCur->pNext;
        pCur->pNext = m_pFreeEventRec;
        m_pFreeEventRec = pCur;
        pCur = pNext;
    }

    m_pFirstEventRec = NULL;
}

CTimer::CTimer()
{
    LARGE_INTEGER liFreq;

    if (!QueryPerformanceFrequency(&liFreq)) ExitFatal(0x5000,"timer initialization");
    if (!QueryPerformanceCounter(&m_liStartCounter)) ExitFatal(0x5001,"timer initialization");

    m_liCurrentCounter = m_liStartCounter;
    m_lfInvFreq = 1/((double)(liFreq.QuadPart));
    m_lfTime = 0.0;
    m_fTimeDelta = 1/INITIAL_FPS;
}

void CTimer::Update()
{
    if (!m_bPaused)
    {
        if (!QueryPerformanceCounter(&m_liCurrentCounter)) ExitFatal(0x5002,"timer update");
        double lfTime = ((double)(m_liCurrentCounter.QuadPart-m_liStartCounter.QuadPart))*m_lfInvFreq;
        m_fTimeDelta = lfTime-m_lfTime;
        if (m_fTimeDelta>MAX_TIME_DELTA) m_fTimeDelta = MAX_TIME_DELTA;
        m_lfTime = lfTime;
        return;
    }

    m_fTimeDelta = 0.0;
}

void CTimer::TogglePause()
{
    LARGE_INTEGER liCounter;

    if (!m_bPaused)
    {
        m_bPaused = true;
        return;
    }

    if (!QueryPerformanceCounter(&liCounter)) ExitFatal(0x5003,"timer toggle pause");
    m_liStartCounter.QuadPart+= liCounter.QuadPart-m_liCurrentCounter.QuadPart;
    m_liCurrentCounter = liCounter;
    m_bPaused = false;
}

CTest::CTest()
{
    m_uCounter = 0;
}

void CTest::ToggleCounter()
{
    ++m_uCounter;
    if (m_uCounter>MAX_COUNTER) m_uCounter = 0;
}

Полный текст программы с бинарником - по ссылке.

A:

В общем, я докопался до истины и понял, где именно кроется ошибка. 
Приведу сначала разобранный мною код тела функции main() полностью
.text:00404070 ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:00404070                 public _main
.text:00404070 _main           proc near               ; CODE XREF: sub_4011A0+91↑p
.text:00404070
.text:00404070 argc            = dword ptr  8
.text:00404070 argv            = dword ptr  0Ch
.text:00404070 envp            = dword ptr  10h
.text:00404070
.text:00404070 ; __unwind {
.text:00404070                 push    ebp
.text:00404071                 mov     ebp, esp
.text:00404073                 push    edi
.text:00404074                 push    esi
.text:00404075                 push    ebx
.text:00404076                 xor     ebx, ebx
.text:00404078                 and     esp, 0FFFFFFF0h
.text:0040407B                 sub     esp, 20h
.text:0040407E                 call    ___main
.text:00404083                 mov     dword ptr [esp], offset aThisApplicatio ; "This application demonstrates the MinGW"...
.text:0040408A                 fld1
.text:0040408C                 fadd    ds:dbl_408058
.text:00404092                 fstp    qword ptr [esp+18h]
.text:00404096                 call    _puts
.text:0040409B
.text:0040409B Do_MainApplicationLoop:                 ; CODE XREF: _main+146↓j
.text:0040409B                                         ; _main+166↓j
.text:0040409B                 mov     ecx, offset _g_Timer
.text:004040A0                 call    __ZN6CTimer6UpdateEv ; CTimer::Update(void)
.text:004040A5                 mov     ecx, offset _g_In
.text:004040AA                 call    __ZN6CInput6UpdateEv ; CInput::Update(void)
.text:004040AF                 mov     edx, ds:m_pFirstEventRec ; EDX (m_pFirstEventRec) = m_pFirstEventRec,
.text:004040AF                                         ; т.е., регистру EDX компилятор ставит в соответствие поле m_pFirstEventRec!
.text:004040B5                 mov     esi, edx        ; ESI (pCur) = m_pFirstEventRec
.text:004040B7                 test    edx, edx
.text:004040B9                 jz      Put_Star        ; вывести очередной '*' по таймеру и продолжить цикл...
.text:004040BF                 mov     edi, [edx+4]    ; EDI = pCur->uEvent
.text:004040C2                 cmp     edi, 101Bh      ; is first event VK_ESC?
.text:004040C8                 jz      RemoveFirstEvent_And_Exit
.text:004040CE                 mov     ecx, edx        ; ECX (pCur) = m_pFirstEventRec
.text:004040D0                 jmp     short FollowLoop_CheckEvent_ESC ; EAX (pCur) = pCur->pNext; ECX (pPrev)
.text:004040D0 ; ---------------------------------------------------------------------------
.text:004040D2                 align 10h
.text:004040E0
.text:004040E0 Loop_CheckEvent_ESC:                    ; CODE XREF: _main+83↓j
.text:004040E0                 cmp     dword ptr [eax+4], 101Bh ; is current event VK_ESC?
.text:004040E7                 jz      RemoveCurrentEvent_And_Exit ; (EAX==pCur, ECX==pPrev)
.text:004040ED                 mov     ecx, eax        ; ECX (pCur) = EAX (pCur)
.text:004040EF
.text:004040EF FollowLoop_CheckEvent_ESC:              ; CODE XREF: _main+60↑j
.text:004040EF                 mov     eax, [ecx]      ; EAX (pCur) = pCur->pNext; ECX (pPrev)
.text:004040F1                 test    eax, eax
.text:004040F3                 jnz     short Loop_CheckEvent_ESC ; is current event VK_ESC?
.text:004040F5                 cmp     edi, 1020h      ; is first event VK_SPACE?
.text:004040FB                 jnz     short FollowLoop_CheckEvent_SPACE ; EAX (pCur) = pCur->pNext; ESI (pPrev)
.text:004040FD                 jmp     RemoveFirstEvent_And_TogglePause ; EAX (pCur) = EDX (m_pFirstEventRec)
.text:004040FD ; ---------------------------------------------------------------------------
.text:00404102                 align 10h
.text:00404110
.text:00404110 Loop_CheckEvent_SPACE:                  ; CODE XREF: _main+B3↓j
.text:00404110                 cmp     dword ptr [eax+4], 1020h ; is next event VK_SPACE?
.text:00404117                 jz      RemoveCurrentEvent_And_TogglePause ; EDX (pCur->pNext) = pCur->pNext
.text:0040411D                 mov     esi, eax
.text:0040411F
.text:0040411F FollowLoop_CheckEvent_SPACE:            ; CODE XREF: _main+8B↑j
.text:0040411F                 mov     eax, [esi]      ; EAX (pCur) = pCur->pNext; ESI (pPrev)
.text:00404121                 test    eax, eax
.text:00404123                 jnz     short Loop_CheckEvent_SPACE ; is next event VK_SPACE?
.text:00404125
.text:00404125 CheckFirstEvent_TAB:                    ; CODE XREF: _main+1CF↓j
.text:00404125                 cmp     dword ptr [edx+4], 1009h ; is first event VK_TAB?
.text:0040412C                 jz      short RemoveFirstEvent_And_ToggleCounter ; EAX (pCur) = m_pFirstEventRec
.text:0040412E                 mov     ecx, edx
.text:00404130                 jmp     short FollowLoop_CheckEvent_TAB ; EAX (pCur) = pCur->pNext; ECX (pPrev)
.text:00404130 ; ---------------------------------------------------------------------------
.text:00404132                 align 10h
.text:00404140
.text:00404140 Loop_CheckEvent_TAB:                    ; CODE XREF: _main+DF↓j
.text:00404140                 cmp     dword ptr [eax+4], 1009h
.text:00404147                 jz      short RemoveCurrentEvent_And_ToggleCounter ; ESI = pCur->pNext
.text:00404149                 mov     ecx, eax
.text:0040414B
.text:0040414B FollowLoop_CheckEvent_TAB:              ; CODE XREF: _main+C0↑j
.text:0040414B                 mov     eax, [ecx]      ; EAX (pCur) = pCur->pNext; ECX (pPrev)
.text:0040414D                 test    eax, eax
.text:0040414F                 jnz     short Loop_CheckEvent_TAB
.text:00404151                 mov     ecx, ds:m_pFreeEventRec ; ECX = m_pFreeEventRec
.text:00404157                 jmp     short Start_FlushEvents ; Вход в метод FlushEvents(),
.text:00404157                                         ; в регистре EDX, как думает компилятор, содержится поле m_pFirstEventRec, однако это не так!
.text:00404157                                         ; EAX (pNext) = pCur->pNext
.text:00404159 ; ---------------------------------------------------------------------------
.text:00404159
.text:00404159 RemoveFirstEvent_And_ToggleCounter:     ; CODE XREF: _main+BC↑j
.text:00404159                 mov     eax, edx        ; EAX (pCur) = m_pFirstEventRec
.text:0040415B                 mov     ecx, offset m_pFirstEventRec ; ecx (pPrev) = &m_pFirstEventRec
.text:00404160
.text:00404160 RemoveCurrentEvent_And_ToggleCounter:   ; CODE XREF: _main+D7↑j
.text:00404160                 mov     esi, [eax]      ; ESI = pCur->pNext
.text:00404162                 mov     edi, 0
.text:00404167                 mov     [ecx], esi      ; pPrev->pNext = pCur->pNext
.text:00404167                                         ; здесь поле m_pFirstEventRec неявно меняется, однако в EDX - по-прежнему старое значение!
.text:00404169                 mov     ecx, ds:m_pFreeEventRec ; ecx (m_pFreeEventRec) = m_pFreeEventRec
.text:0040416F                 mov     [eax], ecx      ; pCur->pNext = m_pFreeEventRec
.text:00404171                 mov     ecx, eax        ; ECX (m_pFreeEventRec) = pCur
.text:00404173                 mov     ds:m_pFreeEventRec, eax ; m_pFreeEventRec = pCur
.text:00404178                 mov     eax, ds:_g_Test
.text:0040417D                 inc     eax
.text:0040417E                 cmp     eax, 1
.text:00404181                 cmova   eax, edi
.text:00404184                 mov     ds:_g_Test, eax
.text:00404189                 jmp     short Start_FlushEvents ; компилятор ошибочно считает, что m_pFirstEventRec не изменилось,
.text:00404189                                         ; по-этому передаёт коду FlushEvents() уже невалидный EDX вместо m_pFirstEventRec!
.text:00404189 ; ---------------------------------------------------------------------------
.text:0040418B                 align 10h
.text:00404190
.text:00404190 Loop_FlushEvents:                       ; CODE XREF: _main+12A↓j
.text:00404190                 mov     edx, eax        ; edx (pCur) = pNext
.text:00404192
.text:00404192 Start_FlushEvents:                      ; CODE XREF: _main+E7↑j
.text:00404192                                         ; _main+119↑j
.text:00404192                 mov     eax, [edx]      ; Вход в метод FlushEvents(),
.text:00404192                                         ; в регистре EDX, как думает компилятор, содержится поле m_pFirstEventRec, однако это не так!
.text:00404192                                         ; EAX (pNext) = pCur->pNext
.text:00404194                 mov     [edx], ecx      ; pCur->pNext = m_pFreeEventRec
.text:00404196                 mov     ecx, edx        ; m_pFreeEventRec = pCur
.text:00404198                 test    eax, eax        ; pNext==NULL?
.text:0040419A                 jnz     short Loop_FlushEvents ; edx (pCur) = pNext
.text:0040419C                 mov     ds:m_pFreeEventRec, edx
.text:004041A2
.text:004041A2 Put_Star:                               ; CODE XREF: _main+49↑j
.text:004041A2                                         ; _main+1D5↓j
.text:004041A2                 fld     qword ptr [esp+18h] ; вывести очередной '*' по таймеру и продолжить цикл...
.text:004041A6                 fld     ds:dbl_408058
.text:004041AC                 mov     ds:m_pFirstEventRec, ebx
.text:004041B2                 fcomip  st, st(1)
.text:004041B4                 ffreep  st
.text:004041B6                 jb      Do_MainApplicationLoop
.text:004041BC                 mov     dword ptr [esp], '*' ; int
.text:004041C3                 call    _putchar
.text:004041C8                 fld     qword ptr [esp+18h]
.text:004041CC                 fadd    ds:flt_40619C
.text:004041D2                 fstp    qword ptr [esp+18h]
.text:004041D6                 jmp     Do_MainApplicationLoop
.text:004041DB ; ---------------------------------------------------------------------------
.text:004041DB
.text:004041DB RemoveFirstEvent_And_Exit:              ; CODE XREF: _main+58↑j
.text:004041DB                 mov     eax, edx
.text:004041DD                 mov     ecx, offset m_pFirstEventRec
.text:004041E2
.text:004041E2 RemoveCurrentEvent_And_Exit:            ; CODE XREF: _main+77↑j
.text:004041E2                 mov     edx, [eax]      ; (EAX==pCur, ECX==pPrev)
.text:004041E4                 mov     [ecx], edx
.text:004041E6                 mov     edx, ds:m_pFreeEventRec
.text:004041EC                 mov     [eax], edx
.text:004041EE                 mov     dword ptr [esp], offset aExiting ; "<exiting...>>"
.text:004041F5                 mov     ds:m_pFreeEventRec, eax
.text:004041FA                 call    _puts
.text:004041FF                 lea     esp, [ebp-0Ch]
.text:00404202                 xor     eax, eax
.text:00404204                 pop     ebx
.text:00404205                 pop     esi
.text:00404206                 pop     edi
.text:00404207                 pop     ebp
.text:00404208                 retn
.text:00404209 ; ---------------------------------------------------------------------------
.text:00404209
.text:00404209 RemoveFirstEvent_And_TogglePause:       ; CODE XREF: _main+8D↑j
.text:00404209                 mov     eax, edx        ; EAX (pCur) = EDX (m_pFirstEventRec)
.text:0040420B                 mov     esi, offset m_pFirstEventRec ; ESI (pPrev) = &m_pFirstEventRec
.text:00404210
.text:00404210 RemoveCurrentEvent_And_TogglePause:     ; CODE XREF: _main+A7↑j
.text:00404210                 mov     edx, [eax]      ; EDX (pCur->pNext) = pCur->pNext
.text:00404212                 mov     ecx, offset _g_Timer
.text:00404217                 mov     [esi], edx      ; pPrev->pNext = pCur->pNext
.text:00404219                 mov     edx, ds:m_pFreeEventRec
.text:0040421F                 mov     [eax], edx      ; pCur->pNext = m_pFreeEventRec
.text:00404221                 mov     ds:m_pFreeEventRec, eax ; m_pFreeEventRec = pCur
.text:00404226                 call    __ZN6CTimer11TogglePauseEv ; CTimer::TogglePause(void)
.text:0040422B                 mov     dword ptr [esp], offset aToggleTimerPau ; "\n<toggle timer pause>"
.text:00404232                 call    _puts
.text:00404237                 mov     edx, ds:m_pFirstEventRec ; EDX (m_pFirstEventRec) = m_pFirstEventRec
.text:0040423D                 test    edx, edx
.text:0040423F                 jnz     CheckFirstEvent_TAB ; is first event VK_TAB?
.text:00404245                 jmp     Put_Star        ; вывести очередной '*' по таймеру и продолжить цикл...
.text:00404245 ; } // starts at 404070
.text:00404245 _main           endp

А теперь рассмотрю подробнее главные моменты. Первый:
.text:004040AF                 mov     edx, ds:m_pFirstEventRec ; EDX (m_pFirstEventRec) = m_pFirstEventRec,
.text:004040AF                                         ; т.е., регистру EDX компилятор ставит в соответствие поле m_pFirstEventRec!

Здесь компилятор заносит значение поля m_pFirstEventRec в регистр EDX и ставит его в соответствие с этим полем.
Следующий момент - ключевой:
.text:00404159 RemoveFirstEvent_And_ToggleCounter:     ; CODE XREF: _main+BC↑j
.text:00404159                 mov     eax, edx        ; EAX (pCur) = m_pFirstEventRec
.text:0040415B                 mov     ecx, offset m_pFirstEventRec ; ecx (pPrev) = &m_pFirstEventRec

Здесь мы видим, что в регистр EAX записывается значение EDX, т.е. m_pFirstEventRec. Это переменная pCur. Следующая инструкция в регистр ECX заносит указатель на m_pFirstEventRecт. Тут у нас переменная pPrev. Этим двум инструкциям соответствуют следующие строки в исходнике:
EVENT_RECORD* pCur = m_pFirstEventRec;
EVENT_RECORD* pPrev = (EVENT_RECORD*)&m_pFirstEventRec; //the source of optimizer's BUG!!!

Рассмотрим последующие три инструкции:
.text:00404160 RemoveCurrentEvent_And_ToggleCounter:   ; CODE XREF: _main+D7↑j
.text:00404160                 mov     esi, [eax]      ; ESI = pCur->pNext
.text:00404162                 mov     edi, 0
.text:00404167                 mov     [ecx], esi      ; pPrev->pNext = pCur->pNext
.text:00404167                                         ; здесь поле m_pFirstEventRec неявно меняется, однако в EDX - по-прежнему старое значение!

Конкретно нас интересуют первая и третья из них. Они соответствуют строке:
pPrev->pNext = pCur->pNext;

И в них значение поля m_pFirstEventRec НЕЯВНО меняется! Однако компилятор это не просёк, он по-прежнему считает, что регистр EDX идентичен содержимому m_pFirstEventRec!
И как результат:
jmp     short Start_FlushEvents ; компилятор ошибочно считает, что m_pFirstEventRec не изменилось,
.text:00404189                                         ; по-этому передаёт коду FlushEvents() уже невалидный EDX вместо m_pFirstEventRec!

......................................................................................
 .text:00404192 Start_FlushEvents:                      ; CODE XREF: _main+E7↑j
    .text:00404192                                         ; _main+119↑j
    .text:00404192                 mov     eax, [edx]      ; Вход в метод FlushEvents(),
    .text:00404192                                         ; в регистре EDX, как думает компилятор, содержится поле m_pFirstEventRec, однако это не так!
    .text:00404192                                         ; EAX (pNext) = pCur->pNext
    .text:00404194                 mov     [edx], ecx      ; pCur->pNext = m_pFreeEventRec
    .text:00404196                 mov     ecx, edx        ; m_pFreeEventRec = pCur
    .text:00404198                 test    eax, eax        ; pNext==NULL?
    .text:0040419A                 jnz     short Loop_FlushEvents ; edx (pCur) = pNext

Т.е., переменной pCur метода FlushEvents() на старте вместо значения поля m_pFirstEventRec подсовывается уже невалидный регистр EDX (который, по сути - уже m_pFreeEventRec)! И всё, программа сломана!
Подведу краткий итог выше приведенному разбору. Компилятор в целях оптимизации кэширует значения переменных и полей классов в регистрах. Однако в данном примере он не понял, что фактическое значение поля в памяти изменилось и более не соответствует его аналогу в регистре. Как итог - сгенерировал поломанный код.
Как это можно исправить? Ну во-первых, скомпилировать с опцией -fno-strict-aliasing, как мне посоветовали в комментариях. Это работает. Однако страдает оптимизация, так что не вариант.
Второй способ: объявить поле m_pFirstEventRec в классе как volatile:
EVENT_RECORD*   volatile m_pFirstEventRec;

Этот модификатор как раз отключает кеширование в регистрах для заданной переменной, чтобы гарантировать её корректную синхронизацию между потоками. Но и в нашем случае он работает. Тем не мнее это выглядит скорее костылём, нежели решением.
Так что, скрепя сердцем, я выбрал для себя третий вариант - переписать метод CheckEvents():
//non-buggy version of method
bool CInput::CheckEvent(UINT uEvent)
{
    EVENT_RECORD* pCur = m_pFirstEventRec;

    if (pCur)
    {
        if (pCur->uEvent==uEvent)
        {
            m_pFirstEventRec = pCur->pNext;
            pCur->pNext = m_pFreeEventRec;
            m_pFreeEventRec = pCur;
            return true;
        }

        while (true)
        {
            EVENT_RECORD* pPrev = pCur;
            pCur = pCur->pNext;
            if (!pCur) return false;

            if (pCur->uEvent==uEvent)
            {
                pPrev->pNext = pCur->pNext;
                pCur->pNext = m_pFreeEventRec;
                m_pFreeEventRec = pCur;
                return true;
            }
        }
    }

    return false;
}

Проблема решена. Всем ответившим в комментариях большое спасибо! А также спасибо, что досмотрели мой опус до конца)))

