getdiskfreespaceex仅返回卷级空闲空间,无法反映单个文件物理扇区占用;精确获取需windows用fsctl_query_allocated_ranges+簇大小计算,linux用fiemap+st_blksize换算,但均不包含元数据开销且受压缩/稀疏/加密影响。

Windows 下用 GetDiskFreeSpaceEx 只能看卷级空闲空间,不反映单个文件的扇区占用
它返回的是卷(比如 C:\)的总容量、可用空间等,和文件本身是否稀疏、是否被压缩、是否跨多个区域完全无关。想靠它算出某个 test.txt 占了多少物理扇区?行不通。
真正能查文件实际磁盘占用的只有 GetCompressedFileSize 和 GetFileInformationByHandle
GetCompressedFileSize 在启用了 NTFS 压缩或稀疏文件的场景下会返回「实际写入磁盘的字节数」,但它不区分扇区对齐、元数据开销、MFT 条目等,结果是近似值;更可靠的是用 GetFileInformationByHandle 拿到 BY_HANDLE_FILE_INFORMATION 结构体里的 nFileSizeHigh/nFileSizeLow(逻辑大小)和 nNumberOfLinks(硬链接数),但这俩还是逻辑层信息。
要逼近真实扇区占用,得结合以下操作:
- 先用
CreateFile以FILE_FLAG_NO_BUFFERING打开文件(否则缓存干扰底层布局) - 调用
DeviceIoControl+FSCTL_GET_VOLUME_BITMAP或FSCTL_QUERY_ALLOCATED_RANGES获取该文件实际分配的簇范围 - 把每个分配范围的长度加起来,再乘以每簇扇区数(
dwBytesPerSector × dwSectorsPerCluster)
注意:FSCTL_QUERY_ALLOCATED_RANGES 要求文件句柄有 FILE_READ_DATA 权限,且仅支持 NTFS 卷;对压缩/加密文件,返回的是解压前的分配范围,不是压缩后实际写的扇区。
Linux 下没有直接等价 API,得靠 stat + ioctl + FIEMAP
stat.st_blocks × 512 是最常用近似值,但它按“512 字节块”统计,且在 ext4 上默认四舍五入到最近的 1024 字节,误差可能达 1KB;更准的做法是用 ioctl(fd, FS_IOC_FIEMAP, &fiemap),它能返回文件所有已分配的逻辑块范围(fm_start, fm_length),再换算成扇区数:
struct fiemap fm = {0};
fm.fm_start = 0;
fm.fm_length = ~0UL; // 查整个文件
fm.fm_flags = FIEMAP_FLAG_SYNC;
ioctl(fd, FS_IOC_FIEMAP, &fm);
// 遍历 fm.fm_extents[] 累加 fm_extent[i].fe_length
坑点:如果文件是稀疏的,FIEMAP 会跳过未分配区域;但若开启了 ext4 的 inline_data 特性,小文件可能根本没分配外部块,此时 st_blocks 为 0,而 FIEMAP 返回空数组——得额外检查 st_size 是否非零且 st_blocks == 0。
跨平台别硬刚扇区,优先用逻辑块数 + 文件系统特性推断
真实扇区数依赖太多变量:NTFS 的压缩单元大小、ext4 的 block size、是否启用 DAX、SSD 的 FTLC 映射、甚至 TRIM 后的释放状态。C++ 层面没有稳定、可移植的方式精确拿到「当前时刻磁盘上该文件占据的物理扇区编号总数」。
实践中更可行的路径是:
- Windows:用
FSCTL_QUERY_ALLOCATED_RANGES得到分配的簇数,再乘以每簇扇区数(从GetDiskFreeSpace拿) - Linux:用
FIEMAP得到分配的 block 数,再乘以st_blksize(不是st_blocks × 512) - 两者都必须处理稀疏、压缩、加密等例外情况,且结果仍是“文件系统视角的已分配空间”,不等于 SSD/NVMe 物理页映射后的实际 NAND 占用
最常被忽略的一点:即使你算出了所有分配范围,也无法得知 MFT 记录、扩展属性、ACL、重解析点这些元数据占了多少额外扇区——它们不计入文件数据流,但确实在磁盘上有物理存在。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











