转换数组和结构

时间:2013-05-13 12:42:18

标签: c sockets casting

假设我有一些复杂的结构

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;

2 个答案:

答案 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

让编译器帮助你。不要做肮脏的演员阵容。当你创建一个缓冲区时,告诉编译器你将使用缓冲区的内容,这样它就可以把它放在内存中的正确位置。