将结构转换为数组?
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), 或已声明 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
.
合并
一个结构直接引用一个连续的内存块,并且结构中的每个字段都位于距开始的某个固定偏移处。然后可以通过结构指针或结构声明的名称访问变量,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以特定的方式使用。
我目前正在学习 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
.
您正在投射 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), 或已声明 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
.
一个结构直接引用一个连续的内存块,并且结构中的每个字段都位于距开始的某个固定偏移处。然后可以通过结构指针或结构声明的名称访问变量,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以特定的方式使用。