将 char* 转换为 struct

Posted

技术标签:

【中文标题】将 char* 转换为 struct【英文标题】:Converting char* to struct 【发布时间】:2017-06-30 07:22:26 【问题描述】:

代码here中有一行:

struct iphdr * iph = (struct iphdr *)buffer;

ProcessPacket 函数中,其中buffer 的类型为char*buffer 已在主函数中由 recvfrom 赋予值。简单字符串(buffer)如何转换为结构,如何安全提取数据?

iphdr:

struct iphdr 
    #if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
    #elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
    #else
        #error  "Please fix <asm/byteorder.h>"
    #endif
         __u8   tos;
         __u16  tot_len;
         __u16  id;
         __u16  frag_off;
         __u8   ttl;
         __u8   protocol;
         __u16  check;
         __u32  saddr;
         __u32  daddr;
         /*The options start here. */
;

【问题讨论】:

buffer 中的数据不是字符串,这是要知道的重要一点。相反,它只是一大块字节,恰好对应于结构。 如果以这种方式写入缓冲区:unit8_t *buffer = (uint8_t *)&amp;yourStruct,那么返回就可以了:yourStruct* str = (yourStruct *)buffer。否则,应该执行memcpy @Some程序员老兄recvfrom编辑一串数据来填充其中的数据包数据。如果它对应于结构,那么打印编辑的字符串也必须包含标题数据,而不仅仅是字符串,但这不会发生。 您收到的数据是二进制数据,不对应任何可以打印的字符。它(再次)是 not 字符串(在 C 或 C++ 意义上)。这就像从文件中读取非文本二进制数据,您希望能够打印它吗?还是将数据用作字符串? 【参考方案1】:

buffer 不是 string。它是一个指向 raw 二进制数据的指针。 recvfrom 用原始 IP/TCP 帧(又名数据包)填充(在本例中,见下文)buffer。因此,buffer 的第一个 sizeof(iphdr) 字节是 IP-header 结构:iphdr。这正是博客作者使用您提供的 sn-p 的原因:

struct iphdr * iph = (struct iphdr *)buffer;

如果包含 IP 标头选项,则标头的实际大小为 iph-&gt;ihl*4

然后在ProcessPacket(在博客中)中检查标头的协议字段(iph-&gt;protocol),以确定数据包包含的传输协议。

如果使用的传输协议是TCP,则可以使用 (sn-p from blog) 提取 TCP 标头(以及后来的数据):

unsigned short iphdrlen = iph->ihl*4;
struct tcphdr *tcph = (struct tcphdr*)(buffer + iphdrlen);

原始帧

博客作者使用以下方法创建了套接字:

sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_TCP);
第一个参数AF_INET表示你想要IPv4数据包(与AF_INET6相反,IPv6)。 第二个参数告诉socket 你想要原始帧 第三个参数 (IPPROTO_TCP) 确保您获得 TCP 帧

或者,如果您想要 UDP 帧,您可以使用:

 sock_raw = socket(AF_INET , SOCK_RAW , IPPROTO_UDP);

如果您贪婪并且想要每个数据包使用(请在使用之前阅读帧格式!):

socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

【讨论】:

出色的解释,但在我的问题的上下文中,另一个答案更合适。谢谢。【参考方案2】:

我认为由于在您的程序过程中执行了以下两行代码,代码引入了未定义的行为:

unsigned char *buffer = (unsigned char *)malloc(65536);
...
struct iphdr *iph = (struct iphdr*)buffer;

buffer 是指向保留为unsigned char* 的内存块的指针,然后将其转换为struct iphdr 类型的指针;然而struct iphdr 很可能与char* 有不同的对齐限制,这是未定义的行为(例如,参见this online c11 draft standard):

6.3.2.3 指针

(7) 指向对象类型的指针可以转换为指向对象类型的指针 不同的对象类型。如果结果指针不正确 为引用类型对齐,则行为未定义。 ...

虽然它可能会起作用(这仍然是 UB 的选项之一),但也可能是程序以您不希望的方式运行。

我建议将信息复制到正确对齐的struct iphdr-object 中:

unsigned char *buffer = (unsigned char *)malloc(65536);
...
struct iphdr iphobj;
memcpy(&iphobj,buffer,sizeof(struct iphdr));
...

然后注意对象的生命周期。

请注意,您标记了代码 CC++,两种语言有不同的规则(例如,关于 malloc 的结果的显式转换,这在 C++ 中是必需的,但在 C 中不鼓励)。

但是关于 UB,我很确定代码在 C 和 C++ 这两种语言中都引入了 UB。

【讨论】:

这仅适用于编译器强制执行严格别名的情况吗?我见过很多 C 代码示例,人们会这样做,但我有点困惑为什么。【参考方案3】:

首先要了解的是,无论(struct iphdr *) 的演员如何,内存中的位都保持完全相同。只是你现在说buffer 现在被视为指向struct iphdr 的指针,而不是以前的指针。您只是告诉编译器用不同的眼镜查看这些位,然后进行相应的解释。编译器突然发现buffer 变成了struct iphdr *。并说“好的”就是这样。重要的是您确切地知道 buffer 是什么并将其转换为正确的类型。

如果您愿意,您可以将buffer 类型转换为int *(或任何其他指针类型),编译器不会说什么。虽然你以后会遇到问题。

【讨论】:

以上是关于将 char* 转换为 struct的主要内容,如果未能解决你的问题,请参考以下文章

将 NSStrings 转换为 C 字符并从 Objective-C 调用 C 函数

无法将 [Struct] 类型的值快速转换为 [string] 类型

如何将 interface 转换为地图

BigQuery - 将通用 JSON 转换为 STRUCT

将大端字节数组转换为 int,如 python 中的 struct.unpack

将VB6类型转换为C#struct