假设我有一些复杂的结构
struct icmphdr
{
u_int8_t type;
u_int8_t code;
u_int16_t checksum;
/* Parts of the packet below don’t have to appear */
union
{
struct
{
u_int16_t id;
u_int16_t sequence;
// Type of ICMP message
// Packet code
// Datagram checksum
} echo;
u_int32_t gateway;
struct
{
u_int16_t __unused;
u_int16_t mtu;
} frag;
} un;
};
和
char buf[SIZE];//for some integer SIZE
这个演员的意义和兴趣是什么?
ip=(struct icmphdr*)buf; //ip was formerly defined as some struct iphdr *ip;
答案 0 :(得分:1)
您的代码背后可能出现的情况是:
程序员想要创建一个数据协议并将各种内容表示为结构,以简化编程并提高代码可读性。
底层API可能只允许以字节为基础进行数据传输。这意味着结构必须作为“字节块”传递。您的特定代码似乎是接收者:它有一大块原始字节,并声明这些字节中的数据对应于结构。
正式&从理论上讲,C标准没有定义在指向不同数据类型的指针之间发生的事情。从理论上讲,任何事情都可能发生。但在实践/现实世界中,只要对数据结构有某种保证,这种演员阵容就是明确定义的。
这是您可以解决问题的地方。许多计算机都有对齐要求,这意味着编译器可以在struct / union内的任何位置自由插入所谓的填充字节。这些填充字节在两个编译之间可能不一定相同,并且它们在两个不同系统之间肯定不会相同。
因此,您必须确保发送方和接收方都没有启用填充,或者它们具有相同的填充。否则你不能使用结构/联合,它们会导致程序崩溃和烧毁。
快速&确保未启用struct padding的脏方法是使用编译器选项,例如非标准#pragma pack 1
,这是许多编译器通常支持的。
专业,可移植的方法是添加一个编译时断言来检查struct的大小是否确实如预期的那样。使用C11,它看起来像
static_assert(sizeof(struct icmphdr) ==
(sizeof(uint8_t) +
sizeof(uint8_t) + ... /* all individual members' types */ ),
"Error: padding detected");
如果编译器不支持static_assert
,有几种方法可以实现与各种宏类似的东西,甚至是运行时assert()
。
答案 1 :(得分:0)
那很糟糕。不要创建一个char缓冲区并将其转换为结构,因为对齐将是错误的(即,char缓冲区将具有一些随机起始地址,因为字符串可以从任何地方开始,但是int需要/应该具有地址的倍数大多数架构上都有四个。)
解决方案不是做那样令人讨厌的演员。建立一个适当的联合,使其具有最严格的成员对齐,或者使用特殊元素强制所需的对齐(如果必须的话)(参见/usr/include/sys/socket.h中sockaddr_storage的定义)或类似的。)
您在堆栈上创建一个缓冲区并将一些数据读入其中:
char buf[1024]; int nread = read(fd, &buf, sizeof(buf));
现在你假装缓冲区是结构:
CHECK(nread >= sizeof(struct icmphdr));
struct icmphdr* hdr = (struct icmphdr*)buf;
hdr->u.gateway; // probable SIGSEGV on eg Itanium!
通过将缓冲区重新解释为结构,我们绕过了编译器的检查。如果我们运气不好,&hdr->u.gateway
将不会是四的倍数,并且在整数上访问它会在某些平台上进行barf。
strut iphdr hdr; int nread = read(fd, &hdr, sizeof(hdr));
CHECK(nread == sizeof(hdr));
hdr.u.gateway; // OK
让编译器帮助你。不要做肮脏的演员阵容。当你创建一个缓冲区时,告诉编译器你将使用缓冲区的内容,这样它就可以把它放在内存中的正确位置。