FILE_FLAG_NO_BUFFERING并非真正“底层”,仅绕过系统缓存;实现低级IO需满足缓冲区地址、读写大小及文件偏移均按扇区对齐(如4096字节),且须用VirtualAlloc等分配对齐内存,否则ReadFile会返回ERROR_INVALID_PARAMETER。

用 CreateFile 打开文件时,FILE_FLAG_NO_BUFFERING 真的等于“底层”吗?
不是。它只是绕过系统缓存,不等于跳过内核、不等于直接和磁盘对话。真正低级 IO 的关键不在标志位,而在你能否控制对齐、缓冲区地址、读写粒度——这些全由 CreateFile 返回的句柄 + ReadFile/WriteFile 决定。
常见错误:开了 FILE_FLAG_NO_BUFFERING 却用普通 malloc 分配缓冲区,结果 ReadFile 直接失败并返回 ERROR_INVALID_PARAMETER。
- 缓冲区地址必须是磁盘扇区大小(通常是 512 或 4096)对齐的,推荐用
VirtualAlloc(返回页面对齐地址)或_aligned_malloc(需手动指定对齐值) - 每次读写字节数必须是扇区大小的整数倍;偏移量(
lpOverlapped->Offset)也必须对齐 - 文件必须以
CREATE_ALWAYS或OPEN_EXISTING打开,且不能是CONIN$或管道等伪设备
ReadFile 和 WriteFile 在非重叠模式下为什么有时“卡住”?
因为它们默认是同步阻塞的——但“卡”的真实原因是文件句柄没设成 FILE_ATTRIBUTE_NORMAL 或被其他进程独占,或者你试图读一个刚创建但还没落盘的文件(尤其在 FILE_FLAG_NO_BUFFERING 下更敏感)。
使用场景:做日志批量写入、内存映射前预分配、或对接硬件驱动时,需要确定性延迟和可控的完成时机。
立即学习“C++免费学习笔记(深入)”;
- 确保打开时用了
GENERIC_READ | GENERIC_WRITE,且没有漏掉FILE_SHARE_READ | FILE_SHARE_WRITE(除非你真要独占) - 检查返回值:
ReadFile返回FALSE时立刻调GetLastError(),常见错误如ERROR_HANDLE_EOF(读到尾)、ERROR_IO_PENDING(误用了重叠句柄却没传OVERLAPPED) - 非重叠模式下,
lpNumberOfBytesRead参数不可为NULL,否则行为未定义
不用 std::vector,怎么安全管理动态缓冲区?
Win32 本身不提供容器,但你可以用 VirtualAlloc + VirtualFree 搭配手动长度跟踪,比 new[] 更贴近底层控制意图——毕竟页对齐、可设保护属性、能跨进程共享(配合 SEC_COMMIT)。
性能影响:每次 VirtualAlloc 至少分配一页(4KB),小缓冲区浪费明显;但大块连续 IO(如 64KB 日志块)时,页表友好、无 malloc 碎片、避免 STL 构造/析构开销。
- 分配示例:
void* buf = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - 释放必须用
VirtualFree(buf, 0, MEM_RELEASE),不能用delete[]或free - 若需零初始化,不要依赖
MEM_COMMIT的清零保证(NTFS 压缩卷可能不满足),显式调SecureZeroMemory或循环赋 0
为什么 SetFilePointerEx 移动位置后 ReadFile 还读错数据?
因为你在用非重叠句柄时,没确认上次 IO 是否真正完成——Win32 文件指针是“逻辑位置”,但底层设备队列可能还在处理上一次请求,尤其在开启 FILE_FLAG_NO_BUFFERING 或对接慢速存储时。
容易踩的坑:把 SetFilePointerEx 当作原子 seek + read 操作,忽略了 IO 完成的时序依赖。
- 非重叠模式下,
SetFilePointerEx成功只表示指针设好了,不代表磁盘已就绪;后续ReadFile才真正触发物理操作 - 如果之前发过异步 IO(比如忘了重置
OVERLAPPED),当前ReadFile可能和旧请求冲突,导致ERROR_INVALID_HANDLE - 调试技巧:用
GetFileSizeEx校验当前文件大小,再用SetFilePointerEx的返回值判断是否越界(返回FALSE+GetLastError() == ERROR_NEGATIVE_SEEK)
真正的底层控制从来不是靠“不用 STL”来体现的,而是你是否清楚每个 Win32 调用背后对应的 IRP 请求类型、是否检查了所有可能的错误分支、以及有没有把缓冲区生命周期和虚拟内存管理绑在一起。这些细节一旦漏掉,代码表面看能跑,实际在高负载或不同磁盘型号下就会崩得毫无征兆。



















