必须用GetAdaptersAddresses(Windows)和getifaddrs(Linux/macOS),禁用硬编码接口名、system命令及ioctl;需正确处理缓冲区、状态过滤、地址长度与内存释放,跨平台时注意链接库、宏定义及MAC格式统一。

Windows 下必须用 GetAdaptersAddresses,别碰 GetAdaptersInfo;Linux/macOS 优先用 getifaddrs,不是 ioctl(SIOCGIFHWADDR);硬编码接口名(如 "eth0" 或 "en0")或调用 system("ip link") 是最常见也最危险的错误起点。
Windows:为什么GetAdaptersAddresses是唯一靠谱选择
旧 API GetAdaptersInfo 在 Server 2012+、Hyper-V 虚拟网卡、WSL2 环境里常返回空数据或错误长度;GetAdaptersAddresses 支持 IPv6、能跳过未连接/虚拟设备,且物理地址字段 PhysicalAddress 和长度 PhysicalAddressLength 是分离存储的——这才是真实硬件反馈。
关键操作点:
- 必须传
AF_UNSPEC作为第一个参数,否则可能漏掉 IPv6 接口下的 MAC - 首次调用要传
nullptr获取缓冲区大小,返回ERROR_BUFFER_OVERFLOW后再 malloc 重试,不这么做大概率 crash 或读越界 - 遍历时检查
OperStatus == IfOperStatusUp(不能只看IfType),否则会拿到已拔线但未释放的旧适配器信息 -
PhysicalAddressLength不一定是 6——某些 USB 网卡、蓝牙 PAN 可能填 8 字节,得严格按该字段取值,别硬写[0]..[5]
Linux/macOS:getifaddrs 比 ioctl 更稳的原因
ioctl(SIOCGIFHWADDR) 对 veth、docker0、macvlan 等虚拟接口经常失败,或返回全零;getifaddrs 返回的是统一结构体链表,所有接口类型都走 AF_PACKET 分支,sockaddr_ll* 中的 sll_addr + sll_halen 才是真实链路层地址。
立即学习“C++免费学习笔记(深入)”;
实操要点:
- 遍历前必须检查
ifa_addr && ifa_addr->sa_family == AF_PACKET,跳过AF_INET/AF_INET6条目(那些是 IP,不是 MAC) -
ifa_name是接口名,可用来过滤"lo"、"docker0"等非物理口,但别依赖它判断“是否可用”——有些设备(如某些 Wi-Fi dongle)会上报wlan0却无有效 MAC - 务必检查
sll_halen >= 6并只取前 6 字节用于以太网场景,避免把 8 字节地址当 MAC 解析 - 调用后必须
freeifaddrs(),否则内存泄漏(这点比 Windows 的 malloc/free 更容易被忽略)
跨平台封装时最容易踩的三个坑
条件编译本身不难,但实际落地时,90% 的失败来自这三个细节:
- Windows 编译时忘了链接
iphlpapi.lib,导致GetAdaptersAddresses链接失败——CMake 里要加target_link_libraries(... iphlpapi),MSVC 项目需手动加库依赖 - Linux 上没开
_GNU_SOURCE宏,getifaddrs声明不可见,编译直接报错;GCC/Clang 需在 include 前定义,或加-D_GNU_SOURCE - MAC 地址格式化逻辑混用大小写:Windows 示例常输出大写
XX-XX-XX...,而/sys/class/net/*/address是小写带冒号(xx:xx:xx...),若后续要做字符串比对(比如白名单校验),大小写不统一就失效
别用 shell 命令解析的真正原因
看似一行 system("ip link show eth0 | grep link/ether") 很快,但它在真实环境里几乎必然崩坏:
- 容器中可能没
ip命令,或只有busybox ifconfig,输出字段顺序和关键词随版本乱变 - 中文系统下
link/ether变成链路/以太网,正则直接失灵 - 嵌入式环境禁用 fork,
system()返回 -1;某些安全策略还会拦截子进程创建 - 每次调用都要启动新进程,毫秒级延迟在高频调用场景(如心跳上报)里会累积成可观开销
真正的跨平台稳定方案,从来不是“找一个命令跑通”,而是接受不同系统有不同抽象层级——Windows 用 IP_ADAPTER_ADDRESSES 链表,Linux/macOS 用 ifaddrs 链表,各自处理好状态过滤和内存生命周期,才是可控的底线。


















