将结构体转换为数组?

6

我正在学习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;这一行代码。
有人能够详细解释一下吗?
6个回答

4

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

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

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

您正在将struct dns_header指针的视图转换为buffer数组。


使用数组比malloc更好吗? - user4793972
2
@user4793972,关于“更好”的问题,这取决于具体情况。在这里,我们知道编译时需要多少空间--如果您需要动态确定空间的数量,则根本不可能实现这一点,而且如果我们需要任何代码能够在函数退出后引用缓冲区,那么它也无法工作(因为任何指向该缓冲区的指针将变得无效...或者说,访问它们的行为将变得未定义...一旦函数退出)。 - Charles Duffy

2
假设你想要为结构体分配空间,这样你就可以...
ptr = malloc(sizeof(struct dns_header)); 

这将返回指向分配的内存的指针。

ptr = (struct dns_header *) &buffer; 

这里的情况与之前几乎相同,只是现在数组是分配在堆栈中的,而且不需要取数组的地址,可以直接使用。

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

或者只是

简单的


ptr = (struct dns_header *) buffer;

这并没有问题,因为地址将是相同的。


1
如果您正在使用malloc,则不能获取指针&buffer的地址,而是获取指针本身的地址(因为它指向由malloc分配的内存):ptr = (struct dns_header *)buffer - Xaqq
我现在用malloc让它工作了,谢谢你指出来。 - user4793972
@user4793972 如果这个答案有帮助的话,您可以接受它。 - Iharob Al Asimi

2
缓冲区只是作为内存区域的服务-它是字符数组并不重要,对于这段代码来说,它可以是任何其他类型的数组,只要它是正确的大小。
结构定义了您如何使用该内存-作为位域,它具有极高的特异性。
话虽如此,假定您正在通过网络发送此结构-执行网络IO的代码可能希望传递以字符数组形式呈现的缓冲区,因为这本质上是最明智的选择-网络IO是以字节的方式完成的。

使用数组而不是malloc函数有什么充分的理由吗? - user4793972
1
@user4793972,这里的数组是在堆栈上分配的,在函数退出时会自动释放,而如果使用malloc,则可以从堆中获取内存,并且需要自行释放它,否则会产生泄漏。 - Charles Duffy

1
我不明白的那一行是 ptr = (struct dns_header *) &buffer。您获取数组的地址,并将其视为指向dns_header的指针。这基本上是原始内存访问,是不安全的,但如果您知道自己在做什么,那就可以。这样做将授予您在数组开头写入dns_header的访问权限。
理想情况下,它应该是dns_header数组而不是字节数组。您必须注意dns_header包含位域,其实现不受标准强制执行,完全取决于编译器供应商。虽然位域实现相当“合理”,但没有保证,因此字节数组的大小实际上可能与您的意图不匹配。

1

除了其他答案之外,此代码自 ANSI C 以来就是非法的。 ptr->q_count = htons(1); 违反了严格别名规则。

只允许使用无声明类型的内存(例如 malloc 分配的空间)或具有 shortunsigned short 或兼容类型声明的内存 (即表达式 ptr->q_count) 来访问 unsigned short lvalue。

要直接使用此代码,您应该向 gcc 或 clang 传递 -fno-strict-aliasing。其他编译器可能有类似的标志或没有。

同样代码的改进版本(还具有结构体大小变更的前向兼容性)如下:

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

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

这是合法的,因为严格别名规则允许 unsigned char 别名任何东西。
请注意,此代码中当前未使用 buffer。如果您的代码实际上是较大代码的较小片段,则可能需要以不同方式定义 buffer。无论如何,它可以与 d 放在一个联合体中。

Matt,你是在说 ptr->q_count = htons(1); 违反了严格别名规则,因为 ptrbuffer 都指向不同类型的同一内存位置,并且 q_count 没有被限定为 char 类型,还是因为 ptr->q_count = htons(1); 赋值本身存在其他问题? - David C. Rankin
@DavidC.Rankin ptr->q_count 的类型为 unsigned short,但它正在访问 unsigned char 类型的内存。 - M.M

0

结构体直接引用了一个连续的内存块,并且结构体中的每个字段都位于距离起始地址一定固定偏移量处。变量可以通过结构体指针或声明名称访问,它们返回相同的地址。

在这里,我们声明了一个“紧凑”的结构体,它引用了一个“连续的内存块”:

#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;

如果我们使用地址原样并适当地进行转换,通常不会出现问题。对于类型为指向数组的指针的数组地址进行解引用将产生相同的指针,但类型为数组类型

虽然以这种方式操作内存可能看起来很方便,但强烈建议不要这样做,因为生成的代码变得难以理解。如果您确实没有选择,我建议使用联合来指定结构体的特定使用方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接