Windows下用GetAdaptersAddresses获取适配器及MAC地址最可靠,需链接Iphlpapi.lib、两次调用获取缓冲区、遍历链表检查PhysicalAddressLength==6且OperStatus==IfOperStatusUp,再格式化输出;Linux用读/sys/class/net/*/address,macOS用getifaddrs过滤AF_LINK。

Windows 下用 GetAdaptersAddresses 获取适配器和 MAC 地址
在 Windows 上,GetAdaptersAddresses 是最可靠、跨版本(XP SP1+)的 API,能同时拿到名称、描述、IPv4/IPv6 配置和 PhysicalAddress(即 MAC)。它比过时的 GetAdaptersInfo 支持 IPv6,且返回结构更清晰。
关键点:
- 必须链接
Iphlpapi.lib,并在代码开头#include <iphlpapi.h></iphlpapi.h> - 调用前先传
NULL获取所需缓冲区大小,再分配内存重试——否则极易返回ERROR_BUFFER_OVERFLOW -
PhysicalAddress字段是字节数组(BYTE PhysicalAddress[8]),长度由PhysicalAddressLength给出,常见为 6(以太网)或 0(虚拟/无 MAC 的接口,如 Loopback、WSL2 vEthernet) - 过滤掉
OperStatus != IfOperStatusUp或ifType == IF_TYPE_SOFTWARE_LOOPBACK可跳过无效适配器
示例片段(仅核心逻辑):
ULONG size = 0;
GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &size);
auto buf = std::vector<BYTE>(size);
auto pAddr = reinterpret_cast<PIP_ADAPTER_ADDRESSES>(buf.data());
if (GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, pAddr, &size) == NO_ERROR) {
for (auto p = pAddr; p; p = p->Next) {
if (p->PhysicalAddressLength == 6 && p->OperStatus == IfOperStatusUp) {
printf("%S → %02X:%02X:%02X:%02X:%02X:%02X\n",
p->FriendlyName,
p->PhysicalAddress[0], p->PhysicalAddress[1],
p->PhysicalAddress[2], p->PhysicalAddress[3],
p->PhysicalAddress[4], p->PhysicalAddress[5]);
}
}
}Linux 下读取 /sys/class/net/ 获取 MAC
Linux 没有统一系统调用,最轻量、最稳定的方式是遍历 /sys/class/net/ 目录,对每个子目录读取 address 文件。这比 ioctl(SIOCGIFHWADDR) 更安全(不依赖 socket、不需 root、不触发 netlink 事件)。
立即学习“C++免费学习笔记(深入)”;
注意点:
- 跳过
lo、docker0、br-等非物理接口:检查device/driver是否存在(真实网卡才有驱动 symlink),或读type文件值是否为1(ARPHRD_ETHER) -
address文件内容是小写十六进制加冒号分隔(如00:1a:2b:3c:4d:5e),但某些虚拟设备可能写00:00:00:00:00:00或报错Permission denied(极少见) - 不要依赖
/proc/net/dev——它只列活跃接口,且不含 MAC
简要 C++ 实现(使用 std::filesystem,C++17):
for (const auto& entry : std::filesystem::directory_iterator("/sys/class/net")) {
std::string name = entry.path().filename().string();
std::string addr_path = "/sys/class/net/" + name + "/address";
std::string type_path = "/sys/class/net/" + name + "/type";
<pre class="brush:php;toolbar:false;">if (!std::filesystem::exists(type_path)) continue;
std::ifstream ifs_type(type_path);
std::string type_str;
std::getline(ifs_type, type_str);
if (type_str != "1") continue; // not ethernet
std::ifstream ifs_addr(addr_path);
std::string mac;
std::getline(ifs_addr, mac);
if (!mac.empty() && mac != "00:00:00:00:00:00") {
std::cout << name << " → " << mac << "\n";
}}
macOS 上用 getifaddrs + AF_LINK 提取 MAC
macOS(及大部分 BSD)用 getifaddrs,但必须筛选 AF_LINK 类型的地址结构(struct sockaddr_dl),才能拿到硬件地址。直接看 AF_INET 或 AF_INET6 的结构体是得不到 MAC 的。
易错细节:
-
sockaddr_dl的sdl_data偏移需用LLADDR()宏计算,不能硬编码+sizeof(struct sockaddr_dl)(因结构体含可变长字段) -
sdl_alen是地址长度,通常为 6;若为 0 表示无有效 MAC(如 utun、ipsec 接口) - 务必检查
ifa_addr->sa_family == AF_LINK,否则reinterpret_cast<struct sockaddr_dl>(ifa_addr)</struct>会越界读 - 接口名(
ifa_name)是 C 字符串,比如"en0"、"bridge0"
关键代码段:
struct ifaddrs* addrs;
if (getifaddrs(&addrs) == 0) {
for (auto it = addrs; it; it = it->ifa_next) {
if (it->ifa_addr && it->ifa_addr->sa_family == AF_LINK) {
auto sdl = reinterpret_cast<struct sockaddr_dl*>(it->ifa_addr);
if (sdl->sdl_alen == 6) {
const uint8_t* mac = reinterpret_cast<const uint8_t*>(LLADDR(sdl));
printf("%s → %02x:%02x:%02x:%02x:%02x:%02x\n",
it->ifa_name,
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
}
}
}
freeifaddrs(addrs);
}跨平台封装要注意的兼容性陷阱
不同系统对“有 MAC”定义不一致:Windows 返回所有适配器(含 Hyper-V、NetAdapter)、Linux 默认暴露所有 net class 设备、macOS 则包含 tunnel 和 utun。真正需要过滤的是那些没有物理链路层的接口。
实操建议:
- 不要仅靠接口名关键字(如
"Loopback"、"vEthernet")过滤——名字不可靠,且多语言系统下会变 - 优先依据底层属性:Windows 看
ifType(IF_TYPE_ETHERNET_CSMACD等)、Linux 看/sys/class/net/*/type、macOS 看sockaddr_dl.sdl_type(IFT_ETHER) - MAC 全零(
00:00:00:00:00:00)或长度非 6 的,一律视为无效,避免误报 - 权限问题在 Linux/macOS 上极少发生(
/sys/class/net和getifaddrs均无需 root),但 Windows 下若程序被沙盒限制(如 UWP),GetAdaptersAddresses可能返回空结果
最麻烦的其实是 WSL2:它在 Linux 层看到的是虚拟以太网卡(eth0),MAC 是随机生成的;而宿主 Windows 层看到的是 vEthernet (WSL),两者 MAC 不同——应用需明确自己要的是哪一层的视图。


















