通过 WINAPI 写入的控制台的大小WriteFile
and WriteConsoleW https://web.archive.org/web/20181222101912/https://learn.microsoft.com/en-us/windows/console/writeconsole据记录有一个模糊定义的限制,如下:
nNumberOfCharsToWrite [in]
要写入的字符数。如果指定的总大小
字符数超出可用堆,该函数失败并显示ERROR_NOT_ENOUGH_MEMORY.
没有记录这指的是哪个“堆”。一个进程可以有多个不同大小的堆(固定或动态)。 NT运行时库中的本机堆实现(例如RtlCreateHeap
)可以在指定地址创建堆,这样可以方便地访问与其他进程共享的内存。使用共享堆通常与本地进程间通信 https://en.wikipedia.org/wiki/Local_Inter-Process_Communication(LPC) 端口——或 NT 6.0+ 中的异步 LPC。 LPC 端口用于在应用程序和系统服务之间传递消息,例如会话管理器 (smss.exe)、服务控制管理器 (services.exe)、本地安全机构 (lsass.exe)、桌面会话服务器 (csrss.exe)和控制台主机 (conhost.exe) 的实例。直接排队到 LPC 端口的消息限制为 256 字节。通过将消息排队到引用共享内存的端口来传递较大的消息。
事实证明,控制台的旧实现(NT 6.3之前)使用LPC作为I/O通道,而上述堆是仅 64 KiB。这是一个特殊的设计选择。我认为有人对用户模式子系统、消息传递酷爱喝得太多了。正确的 NT I/O 使用具有 I/O 系统服务的设备,包括NtCreateFile
, NtReadFile
, NtWriteFile
, and NtDeviceIoControlFile
.
控制台应用程序不知道该堆中有多少可用于写入。 Python 可以从 64 KiB 开始并逐渐减小,但它原始文件 I/O https://docs.python.org/3/library/io.html#raw-file-i-o要求每次调用一次系统调用。相反,它将写入上限限制为 32 KiB,这应该会成功。此限制允许写入最多 16K UTF-16 代码点的宽字符字符串。一个复杂的问题是控制台 I/O 堆栈在 3.6+ 中使用 UTF-8,必须通过MultiByteToWideChar
。目前它只是重复地将 UTF-8 缓冲区分成两半,直到结果长度小于 16K。因此,在问题的示例中,写入 48,889 个字符会减半为 24,444 个字符,然后再次减半为 12,222 个字符。 (IMO,最好尝试写入最多 16K 代码点;获取实际写入的数字,然后调用WideCharToMultiByte
确定写入的 UTF-8 字节数。如果 UTF-8 2-4 字节序列与切点重叠,当前的设计实际上存在错误。)
在 NT 6.3+ (Windows 8.1+) 中,控制台 I/O 没有此大小限制,因为它使用 ConDrv 设备和 I/O 系统调用而不是 LPC。然而,仅仅为了支持无缓冲文本 I/O 堆栈而对代码进行特殊封装是不值得的,如由-u
命令行选项。我们期望交互式控制台 I/O 能够被缓冲。普通的文本 I/O 实际上是不允许的open
称呼。例如:
>>> open('conout$', 'w', buffering=0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: can't have unbuffered text I/O
对 Windows 7 的扩展支持将于 2020 年 1 月 14 日结束,因此 Python 3.8 将是支持它的最后一个版本。 Python 3.9 中应删除控制台写入限制。