GNU 编译器的奇怪行为
Strange behavior from the GNU Compiler
今天我在编写一些代码来收集 Linux 上的网络接口信息时遇到了一些奇怪的事情。我正在使用 ifaddrs 和 ioctl 等标准功能从内核中提取我需要提取的内容。我对大多数 ifaddrs 和 ioctl 函数都不熟悉,所以我正在写行、编译、检查输出、评论事情搞砸了,只是把事情搞得一团糟。最终我偶然发现了这个错误:
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("%s\n", inet_ntoa(((struct sockaddr_in*)&ifr_addr.ifr_addr)->sin_addr));
|
咦...?
起初我认为这是我的老鼠窝 header 设法以某种奇怪的方式使编译器出错:
#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
};
此结构使用 anonymous union,但该功能可从 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.html https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/bits/types/sigevent_t.h.html#_M/sigev_notify_function。标准库中有很多宏。大多数结构成员都有前缀 - ifr_*
,sigevent
为 sigev_*
,siginfo
为 si_*
,timeval
为 tv_*
。这是因为 old C 编译器对所有结构成员名称和变量使用相同的命名空间,而且还因为在极端情况下标准库可能希望使用此类技巧。
总的来说,选择唯一的名称,通常是默默地kind-of假设标准库中的结构成员名称可以是宏。
今天我在编写一些代码来收集 Linux 上的网络接口信息时遇到了一些奇怪的事情。我正在使用 ifaddrs 和 ioctl 等标准功能从内核中提取我需要提取的内容。我对大多数 ifaddrs 和 ioctl 函数都不熟悉,所以我正在写行、编译、检查输出、评论事情搞砸了,只是把事情搞得一团糟。最终我偶然发现了这个错误:
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("%s\n", inet_ntoa(((struct sockaddr_in*)&ifr_addr.ifr_addr)->sin_addr));
|
咦...?
起初我认为这是我的老鼠窝 header 设法以某种奇怪的方式使编译器出错:
#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
};
此结构使用 anonymous union,但该功能可从 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.html https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/bits/types/sigevent_t.h.html#_M/sigev_notify_function。标准库中有很多宏。大多数结构成员都有前缀 - ifr_*
,sigevent
为 sigev_*
,siginfo
为 si_*
,timeval
为 tv_*
。这是因为 old C 编译器对所有结构成员名称和变量使用相同的命名空间,而且还因为在极端情况下标准库可能希望使用此类技巧。
总的来说,选择唯一的名称,通常是默默地kind-of假设标准库中的结构成员名称可以是宏。