我目前正在学习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;
任何人都可以详细解释这个吗?
答案 0 :(得分:4)
您的buffer
只是一个连续的原始字节数组。他们没有buffer
观点的语义:你不能做buffer->ra = 1
之类的事情。
然而,从struct dns_header *
的角度来看,这些字节会变得有意义。您使用ptr = (struct dns_header *) &buffer;
执行的操作是将指针映射到数据。
ptr
现在将指向数据数组的开头。这意味着当您写入值(ptr->ra = 0
)时,实际上是在修改buffer
中的字节0。
您正在投射struct dns_header
数组buffer
指针的视图。
答案 1 :(得分:2)
缓冲区只是作为一个内存区域 - 它的字符数组对于这段代码并不重要;它可以是任何其他类型的数组,只要它的大小正确。
结构定义了你如何使用那个记忆 - 作为一个位域,它具有极高的特异性。
也就是说,大概是你通过网络发送这个结构 - 网络IO的代码可能希望传递一个字符数组形式的缓冲区,因为那个& #39;本质上是最好的选择 - 网络IO在发送字节方面完成。
答案 2 :(得分:2)
假设您要为结构分配空间,以便
ptr = malloc(sizeof(struct dns_header));
将返回指向已分配内存的指针
ptr = (struct dns_header *) &buffer;
几乎是相同的,除了在这种情况下它在堆栈中分配,并且它不需要获取数组的地址,它可以是
ptr = (struct dns_header *) &buffer[0];
或只是
ptr = (struct dns_header *) buffer;
虽然没有问题,因为地址是相同的。
答案 3 :(得分:1)
我不明白的是ptr =(struct dns_header *)& buffer;
您获取数组的地址并假装它是指向dns_header
的指针。它基本上是原始内存访问,这是不安全的,但如果你知道你在做什么就行。这样做将授予您在数组开头写入dns_header
的权限。
理想情况下,它应该是dns_header
的数组而不是字节数组。您必须谨慎对待dns_header
包含位字段的事实,标准不强制执行该字段,这完全取决于编译器供应商。尽管位字段实现相当“理智”,但无法保证,因此字节数组的大小实际上可能与您的意图不匹配。
答案 4 :(得分:0)
添加其他答案:
此代码是非法的,因为ANSI C. ptr->q_count = htons(1);
违反了严格的别名规则。
只允许使用unsigned short
左值(即表达式ptr->q_count
)来访问没有声明类型的内存(例如malloc
'd空格),或已声明类型short
或unsigned 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
结合在一起。
答案 5 :(得分:0)
结构直接引用内存的连续块,并且结构中的每个字段都从开始处位于某个固定的偏移量处。然后可以通过结构指针或返回相同地址的结构声明名称访问变量。
在这里,我们声明一个 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;
如果我们按原样使用地址 并将其正确转换,则通常不会出现问题。将 pointer指针类型的数组地址解引用为array-of-type 会产生相同的指针,但具有不同的 array-of-type 类型。
尽管以这种方式操作内存似乎很方便,但强烈建议不要强烈,因为生成的代码很难理解。如果您真的别无选择,我建议使用联合来指定以特定方式使用该结构。