ftruncate()是最直接、原子性最好的截断/扩展方式,只改文件长度不碰内容、不移动光标;需o_rdwr打开,长度可大于当前大小以生成空洞,截断到0可可靠清空文件。

用 ftruncate() 截断或扩展文件(Linux/macOS)
在类 Unix 系统上,ftruncate() 是最直接、原子性最好的方式——它只操作文件长度,不碰文件内容、不移动光标、不依赖当前 lseek() 位置。
常见错误是传入负数长度或对只读 fd 调用,会返回 -1 并设 errno 为 EINVAL 或 EBADF。
- 必须用
O_RDWR打开文件(O_WRONLY不够,glibc 会拒绝) - 长度可以大于当前大小:空洞自动产生,后续写入才实际分配磁盘块
- 截断到 0 是清空文件的可靠方式(比
open(..., O_TRUNC)更可控) - 示例:
int fd = open("data.bin", O_RDWR); if (fd != -1) { ftruncate(fd, 4096); // 强制设为 4KB,不足补零字节空洞,超长则丢弃 close(fd); }
Windows 上用 SetFilePointerEx() + SetEndOfFile()
Windows 没有等价于 ftruncate() 的单次调用,必须两步走:先定位到目标偏移,再截断/扩展。顺序不能反,否则 SetEndOfFile() 会失败并返回 FALSE。
容易踩的坑是忽略 SetFilePointerEx() 的返回值——它可能因文件太小而定位失败,但很多人只检查 SetEndOfFile()。
- 文件句柄需含
FILE_WRITE_ATTRIBUTES权限(通常GENERIC_WRITE已包含) - 目标长度用
LARGE_INTEGER传入,高位清零;若长度 ≥2GB,必须用SetFilePointerEx,不能用老式SetFilePointer - 扩展时不会初始化新空间内容,读出来是未定义值(不是零)
- 示例:
LARGE_INTEGER li; li.QuadPart = 4096; if (SetFilePointerEx(hFile, li, nullptr, FILE_BEGIN) && SetEndOfFile(hFile)) { // 成功 }
跨平台封装要注意的底层差异
同一段代码在 Linux 和 Windows 上行为不一致的地方,往往不是 bug,而是设计使然。比如“扩展文件”后读取新增区域:
- Linux:
ftruncate()后读新增部分返回全 0(内核保证) - Windows:
SetEndOfFile()后读新增部分是垃圾数据(除非你手动WriteFile()填零) - POSIX 要求
ftruncate()后新增区域读为 0;Windows 文档明确说“内容未定义” - 如果业务逻辑依赖新增区域为零,Windows 必须额外写零(用
WriteFile()或memset()+WriteFile())
为什么不用 std::ofstream::seekp() + write()?
这是新手常试但危险的做法:用 seekp() 跳到末尾后写一个字节,指望“撑大”文件。问题在于:
- 流缓冲区可能延迟写入,
seekp()后不flush(),write()可能静默失败 - 写入位置超出原文件末尾时,中间空洞是否填充、填什么,C++ 标准完全不保证(实际取决于 libc 实现和文件系统)
- 无法可靠截断(
seekp()到中间再write()不会删后面数据) - 多线程下
ofstream的seekp()/write()非原子,竞态风险高
真正需要安全控制文件尺寸时,绕过流、直调系统 API 是唯一靠谱路径。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











