access()是Linux/macOS下检查文件权限最直接的方法,但Windows不支持;需结合errno或GetLastError()判断失败原因,且运行时权限检查不能依赖stat()等静态信息。

用 access() 判断文件权限最直接
Linux/macOS 下,access() 是 C/C++ 标准库中专为权限检测设计的函数,它按当前进程的有效用户/组 ID 检查文件访问能力,行为贴近 shell 的 test -r/-w/-x。Windows 不支持该函数(会返回 -1 并设 errno = ENOSYS),所以必须先做平台判断。
常见错误是直接传入绝对路径却忽略路径不存在或符号链接未解引用的情况——access() 对 dangling symlink 返回 -1 且 errno == ENOENT,容易被误判为“无权限”而非“文件不存在”。
- 检查读权限:
access("path/to/file", R_OK) - 同时检查读+写:
access("path/to/file", R_OK | W_OK) - 执行权限对目录有意义(能否
cd进入),对普通文件则看是否标记了x位 - 务必检查返回值:0 表示有权限,-1 表示失败,需结合
errno区分是权限不足(EACCES)还是路径问题(ENOENT、ENOTDIR)
Windows 下改用 GetFileAttributes() + GetNamedSecurityInfo()
Windows 没有类 Unix 的“用户/组/其他”三段式权限模型,access() 不可用。简单场景下可用 GetFileAttributes() 快速排除只读属性(FILE_ATTRIBUTE_READONLY),但它不反映 ACL 实际效果,比如管理员明明能写,但文件没设只读位,这个函数就完全不报错。
真正可靠的方案是调用 GetNamedSecurityInfo() 获取 DACL,再用 AccessCheck() 模拟当前线程令牌的访问请求——但这涉及安全描述符解析、令牌模拟、权限映射等复杂逻辑,99% 的本地工具类需求其实只需:先 CreateFile() 尝试以 GENERIC_READ | GENERIC_WRITE 打开,失败时看 GetLastError() 是否为 ERROR_ACCESS_DENIED;若成功,立刻 CloseHandle()。这比纯 ACL 解析更贴近真实 IO 行为。
-
GetFileAttributes()只能捕获基础只读标志,不能替代权限检查 - 用
CreateFile()测试时,加FILE_FLAG_NO_BUFFERING或FILE_SHARE_*参数可能干扰结果,保持默认即可 - 注意:UAC 提权后进程的令牌可能和原始登录会话不同,
AccessCheck()需传入正确的HANDLE到线程令牌
stat() 能看到权限位,但不能代替运行时检查
stat() 读取的是文件的 mode 字段(如 st_mode & 0777),在 Linux 上能拿到 S_IRUSR、S_IXGRP 等标志,看起来很全。但它只反映静态权限配置,不考虑:SELinux/AppArmor 策略、mount 选项(如 noexec)、capability(如 cap_dac_override)、甚至 NFS 服务端的导出限制。
典型误用是写个函数只靠 stat() 返回的 mode 做判断,然后在容器里或受限环境中失败——因为容器默认 drop 了 cap_dac_override,即使 mode 显示可写,内核仍拒绝写入。
-
stat()结果只能辅助分析,不能用于决策是否执行 open/write - 对符号链接,
stat()默认跟随,要用lstat()查看链接自身属性 - Windows 的
_stat()不返回有效权限位,st_mode中的_S_IWRITE仅对应只读属性,非 ACL
跨平台封装要注意 errno 和 GetLastError() 的混用
一个函数里既调用 access() 又调用 GetLastError() 是危险的——Windows 下 access() 失败后 GetLastError() 值不可靠,Linux 下调用 Windows API 后 errno 也不可信。必须严格按平台分支处理错误码。
另一个坑是路径分隔符硬编码:"C: empile" 在 C++ 字符串里要写成 "C:\temp\file" 或用 raw string R"(C: empile)",否则反斜杠被当转义。更稳妥的是统一用 /(Windows API 支持),或用 std::filesystem::path 拼接。
- Linux 分支只读
errno,Windows 分支只读GetLastError() - 不要用
perror()或FormatMessage()直接输出,它们依赖当前错误码,跨函数调用易污染 - 测试时务必覆盖挂载点(如
/tmp设了noexec)、网络文件系统(NFS/SMB)、以及 root 用户 vs 普通用户差异
权限不是静态属性,而是内核在每次系统调用时动态评估的结果。哪怕 access() 返回 0,紧接着 open() 仍可能失败——中间可能发生 chown、umask 修改、ACL 更新,或者 mount 选项被管理员临时调整。最保险的做法永远是:直接尝试操作,再根据错误码决定 fallback 行为。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











