最可靠的方法是使用 C++17 起标准库提供的 std::filesystem::permissions(),它跨平台、无需调用系统 API、避免权限模型差异问题,但需注意符号链接默认操作链接本身而非目标,应结合 fs::is_symlink() 和 fs::canonical() 显式处理目标文件。

用 std::filesystem::permissions() 修改文件属性最可靠
标准库从 C++17 开始提供跨平台的 std::filesystem::permissions(),它能直接设置只读、可执行等权限,不用调系统 API,也避开了 Windows 和 Linux 权限模型差异带来的坑。
常见错误是试图用 fopen() 或 ofstream “写失败”来反向判断只读——这只能检测当前进程是否有写权限,不能设属性;还有人用 _chmod()(Windows)或 chmod()(Linux),但得自己处理路径编码、错误码映射、符号链接行为不一致等问题。
- 必须开启 C++17 或更高标准(编译时加
-std=c++17或对应选项) - Windows 下只读位对应
std::filesystem::perms::owner_read | std::filesystem::perms::group_read | std::filesystem::perms::others_read,但更常用的是先清除写位:perms::owner_write | perms::group_write | perms::others_write - Linux/macOS 下
permissions()会真实修改 umask 意义下的权限位;Windows 则仅操作「只读」文件属性(即 Explorer 里勾选的那个复选框),不影响 ACL
#include <filesystem>
namespace fs = std::filesystem;
fs::permissions("config.txt", fs::perms::owner_read | fs::perms::group_read | fs::perms::others_read, fs::perm_options::replace);
// 或更稳妥:先读当前权限,再移除写位
fs::permissions("config.txt", fs::status("config.txt").permissions() & ~fs::perms::owner_write, fs::perm_options::replace);
Windows 下误用 _chmod() 会静默失败
_chmod() 看起来简单,但实际行为和文档描述有偏差:它只对普通文件生效,对目录无效;且在 NTFS 卷上,若文件有 ACL 控制,_chmod() 可能不报错但根本不改属性。
典型现象是调用后检查 GetFileAttributes(),发现 FILE_ATTRIBUTE_READONLY 位没变;或者在资源管理器里右键属性,「只读」复选框仍是未勾选状态。
立即学习“C++免费学习笔记(深入)”;
- 必须传入绝对路径或确保当前工作目录正确,相对路径在多线程下容易出错
- 返回值为 0 表示“可能成功”,但不保证属性已更新;需额外调用
GetFileAttributes()验证 - 不能用于符号链接目标,只会修改链接本身(而链接本身没有只读属性概念)
Linux/macOS 下 chmod() 的权限掩码易写错
直接调 chmod() 不难,但新手常把八进制掩码写成十进制,比如写 chmod("log.txt", 444) —— 这其实是十进制 444(八进制 674),不是只读的 0444。结果文件变成可写,甚至可执行。
另一个坑是忽略 umask:即使传了 0444,如果进程 umask 是 0022,最终权限会是 0444 & ~0022 = 0444(没问题);但若 umask 是 0002,就会变成 0444 & ~0002 = 0444(仍 OK);可一旦你传的是 0644 想设为可读写,umask 就会干扰。
- 务必在数字前加
0写成八进制,如0444、0600 - 若需精确控制,先
umask(0)临时清空(注意线程安全),改完再恢复 - 对目录设权限要小心:
0444会让目录不可进入(缺少执行位),应至少用0555
跨平台封装时别忽略符号链接的处理逻辑
绝大多数场景下,你要改的是链接指向的目标文件,而不是链接本身。但 std::filesystem::permissions() 默认操作符号链接本身(C++ 标准规定),这和 chmod -h 类似,不是你想要的。
Linux 下可用 stat() + chmod() 绕过;Windows 下符号链接极少带权限,通常只需 CreateFile() 打开目标再设属性。但统一逻辑很难写干净。
- 用
fs::is_symlink(p)先判断,再决定是否调fs::canonical(p)获取真实路径 - 注意
fs::canonical()在目标不存在时抛异常,需捕获fs::filesystem_error - 某些 NFS 或网络文件系统不支持
canonical(),此时退回到平台特化逻辑更稳
真正麻烦的从来不是“怎么设只读”,而是“设给谁”——是文件本身?链接?还是链接链末端那个真实文件?这个决策点一旦错,后续所有操作都白忙。










