最可靠设备标识是Windows用WMI查Win32_BaseBoard::SerialNumber、Linux读/sys/class/dmi/id/product_uuid、macOS取IOPlatformUUID;CPU ID、MAC地址、卷序列号等均因可变或权限问题不推荐。

Windows 下用 WMI 读取主板序列号最可靠
主板序列号(SerialNumber)在 Windows 上是相对稳定、无需管理员权限、且硬件绑定强的标识源。它不随重装系统或驱动变更而变,比 CPU ID 或硬盘卷标更适合作为设备指纹。
常见错误是直接调用 Win32_Processor::ProcessorId——这其实是 CPU 特征码(含步进/版本),不同批次同型号 CPU 可能相同;而 Win32_BaseBoard::SerialNumber 才真正对应主板物理标签。
- 必须链接
comsuppw.lib并初始化 COM(::CoInitializeEx(nullptr, COINIT_MULTITHREADED)) - 查询语句用
"SELECT SerialNumber FROM Win32_BaseBoard",别漏掉Win32_前缀 - 返回值可能为空字符串(OEM 主板未写入)或
"None"(常见于虚拟机),需显式判断str.empty() || str == "None" - 在 Windows Server 2012+ 和 Win10/11 上兼容性好,但 Windows Sandbox 或某些轻量容器里 WMI 服务默认关闭
Linux 下优先解析 /sys/class/dmi/id/product_uuid
这个路径在大多数现代发行版(RHEL 8+, Ubuntu 20.04+, Debian 11+)中可直接读取,内容是主板固件写入的 UUID,和 Windows 的 SMBIOS UUID 对应,稳定性接近物理序列号。
注意不是 /proc/sys/kernel/random/uuid——那是内核生成的随机值,每次启动都变;也不是 /sys/class/dmi/id/board_serial,很多设备该文件根本不存在或权限受限。
立即学习“C++免费学习笔记(深入)”;
- 读取前检查文件是否存在且可读:
access("/sys/class/dmi/id/product_uuid", R_OK) == 0 - 读出内容末尾带换行符,要用
std::string::erase(std::find(str.begin(), str.end(), ' '), str.end())清理 - 在 VMware/VirtualBox 中该值固定但虚拟化厂商可控,KVM/QEMU 默认关闭 DMI 暴露,需加启动参数
-smbios type=1,uuid=... - 嵌入式设备(如树莓派、Jetson)通常不提供 DMI 接口,得 fallback 到 SoC serial(
/proc/cpuinfo中的Serial字段)
跨平台时别碰 MAC 地址,尤其在容器或云环境
很多方案试图用网卡 MAC 当唯一 ID,但在 Docker/Kubernetes 里,默认网络模式下容器共享 host 网络命名空间,getifaddrs() 返回的是宿主机接口;若用 bridge 模式,MAC 是动态分配的,重启容器就变。
即使在裸机上,用户也可能禁用网卡、使用 USB 网卡热插拔、或配置多网卡绑定(bonding),导致 AF_PACKET 枚举顺序不稳定,ioctl(SIOCGIFHWADDR) 返回的未必是预期那块卡。
- 不建议用
gethostname()+gethostbyname()拼接——主机名可随意改,DNS 解析还可能失败 - 若真要 fallback 到网络层,只取第一个非 loopback、UP 状态、且
IFF_RUNNING的接口 MAC,并转成小写无分隔格式("001122334455") - macOS 上
IORegistryEntryCreateCFProperty查IOPlatformUUID更稳,但需要链接IOKit.framework,静态链接困难
GetVolumeInformation 读取卷序列号的适用边界
这个 API 返回的是 NTFS/FAT32 卷的 32 位序列号(lpVolumeSerialNumber),不是硬盘物理 ID。它适合“绑定软件到某块系统盘”场景,但不能当设备唯一标识——重装系统、格式化、甚至用 DiskGenius 克隆分区都会生成新序列号。
它快(毫秒级)、无需权限、跨 Win7~Win11 兼容,但仅限本地固定磁盘(DRIVE_FIXED),U 盘或网络映射盘会失败或返回 0。
- 调用前先用
GetDriveType("C:\")确认类型,避免对"Z:\"这类映射盘硬查 - 返回的
DWORD值建议转成十六进制字符串(std::hex ),避免十进制高位溢出误解 - SSD 寿命末期或存在坏块时,某些固件会重映射 LBA,极少数情况下导致卷序列号异常变更
真正难的不是读哪个字段,而是设计 fallback 链:主板序列号 → DMI UUID → 卷序列号 → (最后不得已)加密散列组合多个易变字段。每层都要有明确的失效判断逻辑,而不是堆砌一堆读取然后取第一个非空——那样在虚拟机或精简系统里大概率拿到全空或全假值。










