将结构转换为数组?

Cast struct to array?

我目前正在学习 C,但我无法理解以下代码:

struct dns_header
{
    unsigned char ra : 1;
    unsigned char z : 1;
    unsigned char ad : 1;
    unsigned char cd : 1;
    unsigned char rcode : 4;
    unsigned short q_count : 16;

};

int main(void)
{
    struct dns_header *ptr;
    unsigned char buffer[256];

    ptr = (struct dns_header *) &buffer;

    ptr->ra = 0;
    ptr->z = 0;
    ptr->ad = 0;
    ptr->cd = 0;
    ptr->rcode = 0;
    ptr->q_count = htons(1);

}

我不明白的那一行是ptr = (struct dns_header *) &buffer;

谁能详细解释一下?

缓冲区只是作为一个内存区域——它是一个字符数组对这段代码并不重要;它可以是任何其他类型的数组,只要大小正确

该结构定义了您如何使用该内存 -- 作为一个位域,它以极其具体的方式呈现。

就是说,大概您是通过网络发送这个结构——执行网络 IO 的代码可能希望传递一个字符数组形式的缓冲区,因为这本质上是最明智的选择-- 根据发送 字节 .

完成的网络 IO

假设您要为结构分配 space,这样您就可以

ptr = malloc(sizeof(struct dns_header)); 

这将 return 指向已分配内存的指针,

ptr = (struct dns_header *) &buffer; 

几乎一样,只不过这里是在栈中分配的,不需要取数组的地址,可以是

ptr = (struct dns_header *) &buffer[0];

ptr = (struct dns_header *) buffer;

但这没有问题,因为地址是一样的。

您的 buffer 只是一个连续的原始字节数组。从 buffer 的角度来看,它们没有语义:你不能做像 buffer->ra = 1.

这样的事情

但是,从 struct dns_header * 的角度来看,这些字节将变得有意义。您使用 ptr = (struct dns_header *) &buffer; 所做的是将您的指针映射到您的数据。

ptr 现在将指向数据数组的开头。这意味着当你写一个值(ptr->ra = 0)时,你实际上是在修改buffer.

中的字节0

您正在投射 buffer 数组的 struct dns_header 指针的视图。

The line I don't understand is ptr = (struct dns_header *) &buffer;

你获取数组的地址并假装它是指向 dns_header 的指针。它基本上是原始内存访问,这是不安全的,但如果您知道自己在做什么就可以了。这样做将授予您在数组开头写入 dns_header 的权限。

理想情况下,它应该是 dns_header 的数组而不是字节数组。您必须谨慎对待 dns_header 包含位字段的事实,其实现不是由标准强制执行的,这完全取决于编译器供应商。尽管位域实现相当 "sane",但不能保证,因此字节数组的大小实际上可能与您的意图不匹配。

添加到发布的其他答案:

此代码是非法的,因为 ANSI C。ptr->q_count = htons(1); 违反了严格的别名规则。

只允许使用 unsigned short 左值(即表达式 ptr->q_count)来访问没有声明类型的内存(例如 malloc'd space), 或已声明 shortunsigned short 或兼容的类型。

要按原样使用此代码,您应该将 -fno-strict-aliasing 传递给 gcc 或 clang。其他编译器可能有也可能没有类似的标志。

同一代码的改进版本(也对结构大小更改具有一定的前向兼容性)是:

struct dns_header d = { 0 };
d.q_count = htons(1);

unsigned char *buffer = (unsigned char *)&d;

这是合法的,因为严格的别名规则允许 unsigned char 为任何东西起别名。

请注意,buffer 当前未在此代码中使用。如果您的代码实际上是较大代码的一小段,那么 buffer 可能必须以不同方式定义。在任何情况下,它都可以与 d.

合并

一个结构直接引用一个连续的内存块,并且结构中的每个字段都位于距开始的某个固定偏移处。然后可以通过结构指针或结构声明的名称访问变量,returns 相同的地址。

这里我们声明了一个packed结构体,它引用了一个连续的内存块

#pragma pack(push, 1)
struct my_struct
{
    unsigned char b0;
    unsigned char b1;
    unsigned char b2;
    unsigned char b3;
    unsigned char b4;
};
#pragma pack(pop)

然后可以使用指针通过地址引用该结构。看这个例子:

int main(void)
{
    struct my_struct *ptr;
    unsigned char buffer[5];

    ptr = (struct my_struct *) buffer;

    ptr->b0 = 'h';
    ptr->b1 = 'e';
    ptr->b2 = 'l';
    ptr->b3 = 'l';
    ptr->b4 = 'o';

    for (int i = 0; i < 5; i++)
    {
        putchar(buffer[i]); // Print "hello"
    }

    return 0;
}

这里我们显式映射1:1结构连续内存块到buffer指向的连续内存块(使用第一个元素的地址)。

数组地址和地址的名称在数值上相同,但类型不同。因此,这两行是等价的:

ptr = (struct my_struct *) buffer;
ptr = (struct my_struct *) &buffer;

如果我们按原样使用地址 并适当地转换它,这通常不是问题。将类型为指针的数组地址取消引用为数组类型会产生相同的指针,但具有不同的类型数组类型

尽管以这种方式操作内存似乎很方便,但强烈 不鼓励这样做,因为生成的代码变得非常难以理解。如果实在没办法,我建议使用union来指定struct以特定的方式使用。