为什么在循环中调用 ReadConsole 会破坏堆栈?

6

我使用以下代码禁用了行输入:

DWORD dwConsoleMode;
GetConsoleMode(hStdIn, &dwConsoleMode);
dwConsoleMode ^= ENABLE_LINE_INPUT;
SetConsoleMode(hStdIn, dwConsoleMode);

然后我在循环中调用ReadConsole……在一个循环中:
wchar_t cBuf;

while (1) {
    /* Display Options */

    do {
        ReadConsole(hStdIn, &cBuf, 1, &dwNumRead, NULL);
    } while (!iswdigit(cBuf));

    putwchar(cBuf);

    if (cBuf == L'0') break;
}

如果我运行程序并立即按0键,则程序会干净地退出。
但是,如果我按一堆键,然后按0键,当程序退出时,它会崩溃并出现以下错误信息:
Run-Time Check Failure #2 - Stack around the variable 'cBuf' was corrupted.
为什么会导致堆栈变得损坏?代码很简单,所以我想不出哪里出错了。 下面是可以重现该问题的小程序:
#include <windows.h>
#include <stdio.h>

int wmain(int argc, wchar_t *argv[])
{
    DWORD dwNumRead;
    wchar_t cBuf;

    HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);

    DWORD dwConsoleMode;
    GetConsoleMode(hStdIn, &dwConsoleMode);
    dwConsoleMode ^= ENABLE_LINE_INPUT;
    SetConsoleMode(hStdIn, dwConsoleMode);

    while (true)
    {
        wprintf(L"\nEnter option: ");

        do {
            ReadConsoleW(hStdIn, &cBuf, 1, &dwNumRead, NULL);
        } while (!iswdigit(cBuf));

        putwchar(cBuf);

        if (cBuf == L'0') break;
    }

    return 0;
}

在运行程序后,您需要在按下0键之前猛击键盘,然后它会因栈损坏而崩溃。

我也不能每次都复现这个问题,需要尝试几次。
我是在Visual Studio 2010下运行的,创建了一个新的空控制台项目,并添加了包含该代码的文件。


我无法重现这个问题。你能否提供一个小而完整的程序来展示这个问题? - Harry Johnston
我建议检查 ReadConsoleW 的返回值,并在必要时检查 GetLastError。否则,我一点头绪都没有! - Ken Y-N
1
检查了ReadConsoleW函数的返回值,但每次返回的数值都不为零,因此没有错误。另外当程序发生堆栈破坏时,调试器位于程序结尾处。 - Josh
哇,很棒的发现。 - Sz.
1个回答

9
据我所知,这是 Windows 中的一个 bug。以下是一个稍微简单一些的程序,它展示了这个问题:
#include <windows.h>
#include <crtdbg.h>

int wmain(int argc, wchar_t *argv[])
{
    DWORD dwNumRead;
    wchar_t cBuf[2];

    cBuf[0] = cBuf[1] = 65535;

    HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
    SetConsoleMode(hStdIn, 0);

    while (true)
    {
        _ASSERT(ReadConsoleW(hStdIn, &cBuf[0], 1, &dwNumRead, NULL));
        _ASSERT(dwNumRead == 1);
        _ASSERT(cBuf[1] == 65535);
        Sleep(5000);
    }
}

睡眠使触发问题变得更容易,该问题在您调用ReadConsoleW时等待多个字符时会发生。

在相关断言失败时查看cBuf [1]的内容,似乎ReadConsoleW在缓冲区末尾写入了一个额外的字节。

解决方法很简单:确保您的缓冲区至少有一个额外的字节。 在您的情况下,请使用两个字符数组的第一个字符。


1
有趣。只是好奇,如果我删除Sleep函数,为什么无法触发断点?让它睡眠5秒钟有什么作用? - Josh
2
没有休眠,你很难快速按键以一次读取多个字符,尽管使用剪切和粘贴可能仍然有效。(由于putwchar和wprintf的存在,您的原始代码循环速度较慢,因此休眠并不是必要的。) - Harry Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接