GNU编译器的奇怪行为



我今天在编写一些代码以收集有关Linux上网络接口的信息时遇到了一些奇怪的事情。我正在使用标准功能,如ifaddrsioctl来从内核中提取需要提取的内容。我对大多数用于实现这一点的ifaddrsioctl函数都是新手,所以我正在写行、编译、检查输出、注释内容,只是把事情搞得一团糟。最终我偶然发现了这个错误:

NixNet.h:36:15: error: expected ‘;’ at end of member declaration
36 |  struct ifreq ifr_addr;
|               ^~~~~~~~
NixNet.h:36:15: error: expected unqualified-id before ‘.’ token
36 |  struct ifreq ifr_addr;
|               ^~~~~~~~
RawSock.cpp: In constructor ‘RawSock::RawSock()’:
RawSock.cpp:7:16: error: ‘struct ifreq’ has no member named ‘ifru_addr’
7 |  memset(&this->ifr_addr, 0, sizeof(struct ifreq));
|                ^~~~~~~~
RawSock.cpp:33:15: error: ‘struct ifreq’ has no member named ‘ifru_addr’
33 |  strcpy(this->ifr_addr.ifr_name, this->ifname);
|               ^~~~~~~~
RawSock.cpp:37:28: error: ‘struct ifreq’ has no member named ‘ifru_addr’
37 |  ioctl(sock, SIOCGIFADDR, &ifr_addr);
|                            ^~~~~~~~
RawSock.cpp:44:50: error: ‘struct ifreq’ has no member named ‘ifru_addr’
44 |  printf("%sn", inet_ntoa(((struct sockaddr_in*)&ifr_addr.ifr_addr)->sin_addr));
| 

起初,我认为这是我的一个标题的老鼠窝,它以一种奇怪的方式设法绊倒了编译器:

#include <iostream>
#include <cstdlib>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <pcap/pcap.h>
#include <regex>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if_packet.h>
//#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netdb.h>
__attribute__((constructor)) void RawSock_init();
__attribute__((destructor)) void RawSock_clnp();
void pcap_listalldevs(void);
char* iptos(u_long);
char* ip6tos(struct sockaddr*,char*,int);
void ifprint(pcap_if_t*);
uint32_t getMyIP();
bool getMyMAC(unsigned char*);
extern struct ifaddrs* ifaddr_g;
class RawSock {
private:
uint32_t ifaddr;
uint8_t hwaddr[6];
char ifname[17];
struct ifreq ifr_idx;
struct ifreq ifr_mac;
struct ifreq ifr_addr;
struct sockaddr_ll sock_ll;
int sock;
public:
RawSock();
~RawSock();
int send(void* buf, size_t len);
};

所以我把有问题的声明扔进了一个伪程序:

#include <iostream>
#include <cstring>
#include <linux/if.h>
struct ifreq ifr_addr;
int main(){
memset(&ifr_addr, 0 , sizeof(struct ifreq));
return 0;
}

g++dummy.cpp-g-o dummy-std=c++2a

Test.cpp:4:14: error: expected initializer before ‘.’ token
4 | struct ifreq ifr_addr;
|              ^~~~~~~~
Test.cpp: In function ‘int main()’:
Test.cpp:6:9: error: ‘ifr_ifru’ was not declared in this scope
6 | memset(&ifr_addr, 0, sizeof(struct ifreq));
|         ^~~~~~~~

我把它扔到gcc中,结果相同。此时,唯一要做的就是为变量选择一个不同的名称。这导致了一个干净的编译,所以我在整个存储库中应用了名称更改问题解决了

也许这没什么,但我觉得这真的很奇怪,我很想知道这种行为背后的原因。

ifr_addr是在glibc中定义的宏https://github.com/lattera/glibc/blob/master/sysdeps/gnu/net/if.h#L153

# define ifr_addr   ifr_ifru.ifru_addr  /* address      */

所以你的代码变成:

class RawSock {
private:
//struct ifreq ifr_addr;
struct ifreq ifr_ifru.ifru_addr;
};

无效。


你可以问";为什么&";。为什么ifr_addr是宏?

手册页https://man7.org/linux/man-pages/man7/netdevice.7.html描述了以下结构:

struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
// ... other fields
}; 
//  ^^ anonymous union - no name
};

此结构使用匿名并集,但C11提供了该功能。因此,为了使代码在C11之前不支持匿名联合的编译器下工作,您可以为联合成员命名并执行宏魔术:

struct ifreq {
char ifr_name[IFNAMSIZ]; /* Interface name */
union {
struct sockaddr ifr_addr;
struct sockaddr ifr_dstaddr;
// ... other fields
} some_hidden_name;
};
#define ifr_addr     some_hidden_name.ifr_addr
#define ifr_dstaddr  some_hidden_name.ifr_dstaddr
int main() {
struct ifreq variable;
variable.ifr_addr = stuff;
// becomes:
// variable.some_hidden_name.ifr_addr = stuff;
}

这个把戏是用的-http://www.qnx.com/developers/docs/6.5.0SP1/neutrino/lib_ref/s/sigevent.htmlhttps://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/bits/types/sigevent_t.h.html#_M/sigev_notify_function。标准库中有许多宏。大多数结构成员的前缀为ifr_*sigev_*表示sigeventsi_*表示siginfotv_*表示timeval。这是因为旧的C编译器对所有结构成员名称和变量使用相同的名称空间,但也因为在极端情况下,标准库可能想要使用这样的技巧。

总的来说,选择唯一的名称,通常会默认标准库中的结构成员名称可以是宏。

最新更新