[英]Strange behavior from the GNU Compiler
今天在編寫一些代碼來收集有關 Linux 上的網絡接口的信息時,我遇到了一些奇怪的事情。 我正在使用ifaddrs和ioctl之類的標准功能來提取我需要從 kernel 中提取的內容。 我對大多數用於完成此操作的ifaddrs和ioctl函數都不熟悉,所以我正在編寫行、編譯、檢查 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_*
代表sigevent
, si_*
代表siginfo
, tv_*
代表timeval
。 這是因為舊的C 編譯器對所有結構成員名稱和變量使用相同的命名空間,還因為在極端情況下標准庫可能想要使用這些技巧。
總體而言,選擇唯一的名稱,通常默默地假設標准庫中的結構成員名稱可以是宏。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.