C++hard_link_count硬链接计数
C++里没有hard_link_count?别急,先搞清它到底在哪儿“活”着
你翻遍<filesystem>头文件,搜遍std::filesystem::path的成员函数,甚至把stat结构体从<sys/stat.h>里拎出来逐字段端详——就是找不到一个叫hard_link_count的公开接口。
这不是你漏看了文档,而是C++标准库压根没提供这个东西。
但问题来了:你写了个工具,需要判断某个文件是否被多个硬链接指向;或者你在做资源管理,得确认某块磁盘空间是否还被其他路径“偷偷”引用着;又或者你调试时发现unlink()后文件内容还在,想验证是不是硬链接没清干净……这时候,st_nlink(也就是硬链接计数)不是可选项,是刚需。
关键点在于:C++标准库不暴露它,不等于它不存在;它藏在底层系统调用里,只是需要你亲手把它“接”出来。
std::filesystem::status()和std::filesystem::symlink_status()返回的是file_status对象,它封装了类型、权限等基础信息,但故意剥离了st_nlink这类平台强依赖字段——因为POSIX的st_nlink和Windows的硬链接语义并不完全对等(Windows直到NTFS v5.0才支持硬链接,且受限于管理员权限和同卷限制)。标准委员会选择“宁缺毋滥”,而非给出一个在Windows上可能返回0或抛异常的半吊子接口。
所以,务实的做法是:在需要精确计数的场景,绕过<filesystem>的抽象层,直接调用stat()(POSIX)或GetFileInformationByHandle()(Windows)。这不是倒退,而是分层设计的自然分工——标准库管跨平台共识,系统调用管平台真相。
以Linux/macOS为例,几行代码就能拿到真实值:
#include <sys/stat.h>
#include <string>
int get_hard_link_count(const std::string& path) {
struct stat sb;
if (stat(path.c_str(), &sb) == 0) {
return static_cast<int>(sb.st_nlink);
}
return -1; // 错误:文件不存在或无权限
}
注意这里用的是stat()而非lstat()——如果你要统计的是符号链接自身(而非它指向的目标)的硬链接数,才该用lstat();但硬链接本身不能指向目录,也不会像软链接那样存在“自身 vs 目标”的歧义,所以绝大多数场景下,stat()才是正确选择。
Windows稍麻烦些,得先打开句柄再查:
#include <windows.h>
#include <winbase.h>
int get_hard_link_count_win(const std::wstring& path) {
HANDLE h = CreateFileW(path.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (h == INVALID_HANDLE_VALUE) return -1;
BY_HANDLE_FILE_INFORMATION info;
if (GetFileInformationByHandle(h, &info)) {
CloseHandle(h);
return static_cast<int>(info.nNumberOfLinks);
}
CloseHandle(h);
return -1;
}
你会发现,nNumberOfLinks和st_nlink语义一致:它统计的是该文件数据(inode或MFT记录)被多少个目录项直接引用。删掉一个硬链接,这个数就减1;减到0,系统才真正回收磁盘空间。 这正是为什么rm之后df不立刻变多——只要st_nlink > 1,文件内容就还活着。
有人会问:那能不能用std::filesystem::hard_link_count()这种假想接口?目前不行,C++23也没加。提案P1164曾讨论过扩展file_status,但因跨平台实现分歧太大被搁置。与其等标准,不如自己封装一层轻量适配器:
#ifdef _WIN32
int hard_link_count(const std::filesystem::path& p) {
return get_hard_link_count_win(p.wstring());
}
#else
int hard_link_count(const std::filesystem::path& p) {
return get_hard_link_count(p.string());
}
#endif
这样调用就干净了:int n = hard_link_count("/etc/hosts");。它不破坏原有代码风格,又补上了关键能力。
最后提醒一个易踩的坑:硬链接计数≠文件名数量。比如ln a.txt b.txt && ln a.txt c.txt,a.txt、b.txt、c.txt三个名字指向同一inode,st_nlink是3;但如果你用cp a.txt d.txt,d.txt是全新inode,st_nlink仍是3——复制不增加原文件的链接数。真要统计“有多少路径通向同一份数据”,必须查st_nlink,而不是数find /path -samefile target的结果条数(后者其实也是靠st_ino+st_dev比对,本质相同)。
回到开头那个问题:C++为什么没给hard_link_count?
因为它不是“要不要加”的功能问题,而是“在哪一层加才合理”的架构问题。标准库守住可移植性底线,把真相留给系统调用——这反而给了我们更稳的控制权。
下次看到文件删了却占着空间,别急着重启,先查查它的st_nlink。数字还没归零,说明还有别的名字在守着它。


还没有评论,来说两句吧...