簡體   English   中英

來自 GNU 編譯器的奇怪行為

[英]Strange behavior from the GNU Compiler

今天在編寫一些代碼來收集有關 Linux 上的網絡接口的信息時,我遇到了一些奇怪的事情。 我正在使用ifaddrsioctl之類的標准功能來提取我需要從 kernel 中提取的內容。 我對大多數用於完成此操作的ifaddrsioctl函數都不熟悉,所以我正在編寫行、編譯、檢查 output、注釋掉事情,只是把事情弄得一團糟。 最終我偶然發現了這個錯誤:

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

此結構使用匿名 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;
 }

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 標准庫中有很多宏。 大多數結構成員都有一個前綴 - ifr_*sigev_*代表sigeventsi_*代表siginfotv_*代表timeval 這是因為舊的C 編譯器對所有結構成員名稱和變量使用相同的命名空間,還因為在極端情況下標准庫可能想要使用這些技巧。

總體而言,選擇唯一的名稱,通常默默地假設標准庫中的結構成員名稱可以是宏。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM