判断文件是否为可执行格式必须检查文件头魔数:Windows 验证 MZ + PE 签名(先读 e_magic=0x5A4D,再解析 e_lfanew 定位 PE 头并校验 "PE\0\0"),Linux/macOS 验证 \x7fELF 魔数并进一步解析 e_type 字段(ET_EXEC 或 ET_DYN)及字节序、类标识,不可依赖权限位或 access() 函数。

不能靠扩展名或权限位判断,必须检查文件头魔数——这是操作系统加载器真正依赖的硬规则。
Windows 下用 MZ + PE 签名快速识别
读取前 64 字节即可完成判断,关键不是“打开”,而是二进制读头。常见错误是跳过 e_lfanew 直接硬读偏移 0x3C 处的值,但该偏移本身是从 DOS 头里解析出来的。
-
fseek(f, 0x3C, SEEK_SET)读 4 字节得到e_lfanew,它是个小端 uint32_t,表示 PE 头起始位置 - 再
fseek(f, peHeaderOffset, SEEK_SET),确保文件长度 ≥peHeaderOffset + 4,否则可能越界或伪造头 - 读 4 字节比对是否等于
0x00004550(即 "PE\0\0") - 必须先验证
e_magic == 0x5A4D("MZ"),否则跳转到任意偏移都不可信
Linux/macOS 下优先检查 \x7fELF 魔数
只读前 4 字节,比 Windows 更快更确定——但仅说明是 ELF 格式,不等于能执行。常见误判是用文本模式打开,或只读 1~3 字节就返回结果。
- 用
std::ifstream必须加std::ios::binary标志,否则在 Windows 上换行符会被转换 - 逐字节比对:
buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F',别用strcmp - ARM64/MIPS/PPC 的 ELF 魔数完全一致,无需区分架构
- 注意:
\x7fELF只代表格式合法,不是可执行程序的充分条件
真正可执行需进一步检查 ELF e_type 字段
光有魔数不够,很多 .o 或 .so 文件也带 \x7fELF,但不能直接 execve()。必须解析 ELF header 中的 e_type 字段,且要校验字节序和结构体对齐。
-
e_type在 header 偏移 16 字节处(小端),值为ET_EXEC或ET_DYN才算可执行 - 先读
e_ident[EI_CLASS]判断是ELFCLASS32还是ELFCLASS64,再选对应结构体 - 必须校验
e_ident[EI_DATA]是ELFDATA2LSB(小端)还是ELFDATA2MSB(大端),否则字段解析全错 - 别依赖系统
:macOS/Windows 没这头文件,MSVC 下字段名也可能不一致;手写 packed 结构体最稳
为什么不能用 access(path, X_OK) 或 _access(path, 6)
它只查文件 mode 位或只读属性,跟内容是否为可执行格式毫无关系。一个空文本文件 chmod +x 后,access(..., X_OK) 返回 0,但它显然不是可执行文件。
- Windows 上
_access(..., 6)对 .exe 文件根本不管用,它只看“是否有执行权限”这个 ACL 模拟值 - Linux 上
access(..., X_OK)对普通二进制文件几乎总返回 -1,哪怕它是合法 ELF —— 因为内核不靠这个判断,而是靠魔数 - 跨平台项目里,硬封装一个 “is_executable” 函数只会掩盖语义差异:Windows 认的是 PE,Linux 认的是 ELF,macOS 认的是 Mach-O
- 真正需要判断“能否被系统加载运行”时,魔数 + 格式字段才是唯一通用路径
魔数检测本身很快,但容易漏掉字节序校验、结构体 padding、文件长度边界检查这几个点——它们不会导致 crash,但会让 e_type 解析成随机值,从而把合法可执行文件判成无效。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











