std::filesystem::file_size() 返回逻辑大小而非磁盘实际占用,因后者受文件系统块大小、稀疏文件、压缩等影响;linux 用 stat().st_blocks×512,windows 需结合 getcompressedfilesize() 和稀疏文件探测。

为什么 std::filesystem::file_size() 不等于磁盘实际占用?
因为 file_size() 返回的是文件逻辑大小(即你写入的字节数),而磁盘真实占用取决于文件系统块大小、稀疏文件、硬链接、压缩或透明加密等特性。比如一个 123 字节的文件,在 4096 字节/簇的 NTFS 或 ext4 上,至少占 4096 字节;若该文件是稀疏文件(如用 lseek()+write() 跳过中间区域),file_size() 仍返回总跨度,但磁盘可能只存了几个块。
Linux 下用 stat() 的 st_blocks 字段计算物理占用
st_blocks 是 POSIX 标准字段,单位是 512 字节(不是文件系统块大小),它反映内核统计的实际分配块数(含间接块、ext4 的 extent 等)。只要文件没被截断或并发修改,这个值基本可靠。
实操建议:
- 用
struct stat sb; if (stat("path", &sb) == 0) { off_t physical_bytes = sb.st_blocks * 512; } - 注意:某些文件系统(如 btrfs)在启用压缩时,
st_blocks可能仍按未压缩块数上报,实际磁盘占用更低——此时需查ioctl(BTRFS_IOC_SPACE_INFO)等专用接口 - 不要用
st_blksize去乘逻辑大小,那是建议 I/O 缓冲区大小,和占用无关
Windows 下用 GetDiskFreeSpaceEx() + GetCompressedFileSize() 组合判断
Windows 没有直接等价于 st_blocks 的 API。最接近的是 GetCompressedFileSize(),但它仅对启用了 NTFS 压缩的文件有效;对普通文件,它退化为返回逻辑大小。
实操建议:
- 先调用
GetFileInformationByHandle()获取BY_HANDLE_FILE_INFORMATION.nNumberOfLinks和dwVolumeSerialNumber,排除硬链接干扰(同一文件多个硬链接共享物理块) - 再用
GetCompressedFileSize():若返回值小于GetFileSize(),说明已压缩,该返回值更接近真实占用;否则 fallback 到估算——通过GetDiskFreeSpaceEx()拿到lpSectorsPerCluster和lpBytesPerSector,再用ceil(file_size / (sectors_per_cluster * bytes_per_sector)) * (sectors_per_cluster * bytes_per_sector) - 注意:NTFS 稀疏文件需额外检查
FILE_ATTRIBUTE_SPARSE_FILE,并用DeviceIoControl(FSCTL_QUERY_ALLOCATED_RANGES)枚举实际分配区间,否则估算会严重高估
C++ 跨平台封装要注意的底层差异
没有标准库函数能跨平台返回“真实物理字节数”,std::filesystem 的设计目标就是逻辑视图。强行封装容易掩盖关键语义差异。
实操建议:
- Linux 优先走
stat()+st_blocks;Windows 优先走GetCompressedFileSize()+ 稀疏文件探测;macOS 需用getattrlist(ATTR_CMN_DATAALLOCSIZE)(不是ATTR_CMN_SIZE) - 避免把“物理占用”当作精确值返回——它本身是文件系统快照,可能在你读取后立即被 truncate 或追加
- 如果业务真依赖精确物理统计(如配额管理),必须配合
flock()或LockFileEx()加锁,并接受结果仍有短暂滞后性
物理占用从来不是静态属性,而是特定时刻、特定文件系统实现下的一次采样。别把它当文件元数据缓存,每次需要时重新查。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











