windows下判断保留路径需先规范化并提取文件名,再比对con、prn、aux、nul、com1–com9、lpt1–lpt9等不区分大小写的保留名列表,且必须限定在_win32下执行,配合异常捕获error_invalid_name或error_bad_pathname确保健壮性。

Windows下用 GetFullPathName + IsBadPath 类逻辑不可靠
直接调用 GetFullPathName 并检查返回值是否为 0,不能判断是否为保留路径。它只解析路径语法,对 CON、AUX、NUL 等设备名完全不拦截——这些名字在 Windows 中属于内核级保留名,连创建文件都会失败,但 GetFullPathName 会照常返回有效长度。
真正有效的做法是:先规范化路径(去除多余 ./..、转为绝对路径),再提取末尾的文件/目录名,逐个比对 Windows 保留设备名列表。
- 保留名包括:
CON、PRN、AUX、NUL、COM1–COM9、LPT1–LPT9(不区分大小写) - 它们后面若跟
.或\(如CON.、CON\、CON:xxx)也仍被系统拒绝 -
std::filesystem::canonical在遇到保留名时会抛出std::filesystem::filesystem_error,但异常类型不区分“不存在”和“保留名”,需结合错误码判断
用 std::filesystem::status 检查前先做名称预筛
不要依赖 status 或 exists 的返回结果来反推是否为保留路径——对 NUL 这类名,status 可能返回 file_type::not_found(和真实不存在的路径一样),也可能在某些权限下返回 file_type::character(极少见且不稳定)。
稳妥方式是:手动提取路径最后一段(不含扩展名和冒号),转换为大写后查表:
std::string basename = path.filename().string();
size_t dot_pos = basename.find('.');
if (dot_pos != std::string::npos) basename.erase(dot_pos);
// 去掉末尾可能的 ':'(如 "CON:")
if (!basename.empty() && basename.back() == ':') basename.pop_back();
std::transform(basename.begin(), basename.end(), basename.begin(), ::toupper);
static const std::unordered_set<:string> reserved = {
"CON", "PRN", "AUX", "NUL",
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"
};
return reserved.find(basename) != reserved.end();
</:string>
跨平台代码里必须隔离 Windows 特定逻辑
std::filesystem 标准本身不定义“保留路径”概念,Linux/macOS 下 /dev/null 是合法路径,CON 则只是普通文件名。所有保留路径检查必须加 #ifdef _WIN32 保护。
- 不要在非 Windows 平台调用上述预筛逻辑,否则会误判
- 即使使用
boost::filesystem或 Qt 的QFileInfo,其底层也不自动过滤 Windows 保留名 - 若路径来自用户输入(如 GUI 文件对话框),注意 Explorer 本身已屏蔽部分保留名输入,但命令行或拖入仍可能传入
实际写入前仍需捕获 std::filesystem::filesystem_error
预筛只能覆盖常见保留名,Windows 还有其他限制:路径总长超 MAX_PATH(除非启用长路径支持)、含非法字符( : " / \ | ? *)、以空格或点结尾等。这些不会被预筛捕获。
所以最终落盘操作(如 std::filesystem::create_directories 或 std::ofstream 打开)仍要包裹 try/catch,并检查 ec.value() 是否为 ERROR_INVALID_NAME(123)或 ERROR_BAD_PATHNAME(161):
std::error_code ec;
std::filesystem::create_directories(path, ec);
if (ec.value() == ERROR_INVALID_NAME || ec.value() == ERROR_BAD_PATHNAME) {
// 很可能含保留名或非法结构
}
预筛是第一道防线,异常捕获是最后一道——两者缺一不可。漏掉任意一个,都可能让程序在用户尝试保存为 aux.txt 时静默失败或崩溃。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











