简体   繁体   English

来自 GNU 编译器的奇怪行为

[英]Strange behavior from the GNU Compiler

I came across something strange today when writing some code to gather information about network interfaces on Linux.今天在编写一些代码来收集有关 Linux 上的网络接口的信息时,我遇到了一些奇怪的事情。 I'm using the standard functionality like ifaddrs and ioctl to pull what I need to pull from the kernel.我正在使用ifaddrsioctl之类的标准功能来提取我需要从 kernel 中提取的内容。 I'm new to the most of ifaddrs and ioctl functions used to accomplish this, so I'm writing lines, compiling, checking output, commenting things out, and just making a general mess of things.我对大多数用于完成此操作的ifaddrsioctl函数都不熟悉,所以我正在编写行、编译、检查 output、注释掉事情,只是把事情弄得一团糟。 Eventually I stumbled upon this error:最终我偶然发现了这个错误:

 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));
      | 

Huh...?咦……?

At first I figured it was my rat's nest of a header managing to somehow trip up the compiler in a strange way:起初我认为这是我的老鼠巢 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);
};

So I threw problematic declaration into a dummy program:所以我把有问题的声明扔到一个虚拟程序中:

#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 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));
      |         ^~~~~~~~
 

I threw it in gcc , same result.我把它扔进gcc ,结果相同。 At this point the only thing left to do is choose a different name for the variable.此时,唯一要做的就是为变量选择一个不同的名称。 This resulted in a clean compilation so I applied the name change across my repository.这导致了一个干净的编译,所以我在我的存储库中应用了名称更改。 Problem solved!问题解决了!

Maybe it's nothing but I thought this was really weird and I'd be fascinated to know the reason behind this behavior.也许没什么,但我认为这真的很奇怪,我很想知道这种行为背后的原因。

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

# define ifr_addr   ifr_ifru.ifru_addr  /* address      */

So your code becomes:所以你的代码变成:

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

Which is invalid.这是无效的。


You may ask "Why?".你可能会问“为什么?”。 Why is ifr_addr a macro?为什么ifr_addr是一个宏?

The man page https://man7.org/linux/man-pages/man7/netdevice.7.html describes the following structure:手册页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
 };

This structure uses an anonymous union , but that feature is available from C11.此结构使用匿名 union ,但该功能可从 C11 获得。 So to make the code work under compilers before C11 that do not support anonymous unions, you give the union member a name and do macro magic:因此,要使代码在不支持匿名联合的 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;
 }

This trick is used - 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 . This trick is used - 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 There are many macros in standard library.标准库中有很多宏。 Mostly structure members have a prefix - ifr_* , sigev_* for sigevent , si_* for siginfo , tv_* for timeval .大多数结构成员都有一个前缀 - ifr_*sigev_*代表sigeventsi_*代表siginfotv_*代表timeval That is because old C compilers used same namespace for all structure member names and variables, but also because in extreme situations the standard library may want to use such tricks.这是因为旧的C 编译器对所有结构成员名称和变量使用相同的命名空间,还因为在极端情况下标准库可能想要使用这些技巧。

Overall, pick unique names, generally silently kind-of assume that structure member names from standard library can be macros.总体而言,选择唯一的名称,通常默默地假设标准库中的结构成员名称可以是宏。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM