C ++ struct bit字段无法正确解析数据
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C ++ struct bit字段无法正确解析数据相关的知识,希望对你有一定的参考价值。
我正在尝试使用压缩结构从VLAN标头中提取字段:
我创建了这个结构:
#pragma pack(push, 1)
struct vlan_header
{
uint16_t PCP : 3,
DEI : 1,
ID : 12;
};
#pragma pack(pop)
当我拿一个uint8_t
数组并尝试从中提取字段时:
uint8_t* data;
vlan_header* vlanHeader;
data = new uint8_t[2];
data[0] = 0;
data[1] = 0x14; // data is 00 14
// That means PCP is 0, DEI is 0 and vlan id is 20
vlanHeader = (vlan_header*)data;
std::cout << "PCP: " << vlanHeader->PCP << std::endl;
std::cout << "DEI: " << vlanHeader->DEI << std::endl;
std::cout << "ID: " << vlanHeader->ID << std::endl;
delete[] data;
输出是:
PCP: 0
DEI: 0
ID: 320
显然,我们看到vlan id是320而不是20,这不是我的意思。我认为问题是字节序(我的机器是小端)我不知道如何优雅地解决问题。
也许位字段不适合这项工作?
OP问这个:
我认为问题是字节序(我的机器是小端)我不知道如何优雅地解决问题。
也许位字段不适合这项工作?
虽然在使用位域或工会时,考虑机器的端序始终是一个很好的考虑因素,不应该忘记。但是,在您目前的情况下,我没有看到端点是任何问题的原因或关注点。至于问题的第二部分,一切都取决于具体的需求。如果要编写的代码专门用于特定的体系结构/ os /平台并且不太可能是可移植的,那么如果构造正确,使用位域应该没有任何问题。即使您决定移植到其他机器,您仍然可以使用位域,但您必须更加小心,并且可能必须使用预处理器指令或控制开关和case语句编写更多代码以使代码使用并执行一项操作在一台机器而不是另一台机器上。
当使用位域时,我认为在混合类型时会考虑字节序。
struct Bitfield {
unsigned a : 10,
b : 10,
c : 16;
int x : 10,
y : 10,
z : 16;
};
像上面这样的东西可能需要考虑到endian。
通过查看您的位域结构,我看到的是对位域内位的对齐与结构本身对齐的误解。
您当前的结构是:
#pragma pack(push, 1) struct vlan_header { // uint16_t = 2bytes: - 16bits to work with uint16_t PCP : 3, // bit(s) 0-2 DEI : 1, // bit(s) 3 ID : 12; // bit(s) 4-15 }; #pragma pack(pop)
您正在将对齐打包到1 byte
的最小可能大小,因此此结构中的边界对于每个边界都在8 bits
。没什么大不了的,而且非常自我解释。你正在使用一种uint16_t
,它是typedef
的unsigned short
,它的大小是2 bytes
或者是16 bits
。 unsigned short
的值范围从[0,65535]
。
然后在结构体内,您将位域成员PCP
,DEI
和ID
设置为具有位数:3
,1
,12
。我在你的结构中添加了注释以显示这种模式。
现在,在主函数中声明指向uint8_t
类型的指针,然后创建上述结构的实例,然后为数组大小为[2]
的指针创建动态内存。这里uint8_t
是typedef
的unsigned char
,其大小是1 byte
或8 bits
可以使用,因为你有2
,你总共有2 bytes
或16 bits
。好的,所以内存的总大小在你的bitfield struct
和data[]
数组之间匹配。
然后,通过索引并使用十六进制值设置指针数组来填充指针数组。然后通过将array
的值转换为该类型,将其分配给bitfield
。然而,我认为你假设的是data[0]
应该适用于位域的第一个2
成员,并且data[1]
应该为最后一个ID
值工作。然而,这种情况并非如此:
这里发生的是你的代码的这一部分:
data[0] = 0; data[1] = 0x14; // data is 00 14
以上并没有做你认为应该做的事情。
我将制作一个图表只是为了向您展示示例:但是它太大而无法在此显示;所以我能做的就是为你提供一些代码,让你在你的机器上运行,生成一个日志文件供你查看模式。
#include <iostream>
#include <fstream>
#pragma pack(push, 1)
struct vlan_header {
// uint16_t = 2bytes: - 16bits to work with
uint16_t PCP : 3, // bit(s) 0-2
DEI : 1, // bit(s) 3
ID : 12; // bit(s) 4-15
};
#pragma pack(pop)
int main() {
uint8_t* data; // sizeof(uint8_t) = 1byte - 8bits
vlan_header* vlanHeader;
data = new uint8_t[2];
std::ofstream log;
log.open( "results.txt" );
for ( unsigned i = 0; i < 256; i++ ) {
for ( unsigned j = 0; j < 256; j++ ) {
data[0] = j;
data[1] = i;
std::cout << "data[0] = " << static_cast<unsigned>(data[0]) << " ";
std::cout << "data[1] = " << static_cast<unsigned>(data[1]) << " ";
log << "data[0] = " << static_cast<unsigned>(data[0]) << " ";
log << "data[1] = " << static_cast<unsigned>(data[1]) << " ";
vlanHeader = reinterpret_cast<vlan_header*>(data);
std::cout << "PCP: " << std::hex << vlanHeader->PCP << " ";
std::cout << "DEI: " << std::hex << vlanHeader->DEI << " ";
std::cout << "ID: " << std::hex << vlanHeader->ID << std::endl;
log << "PCP: " << std::hex << vlanHeader->PCP << " ";
log << "DEI: " << std::hex << vlanHeader->DEI << " ";
log << "ID: " << std::hex << vlanHeader->ID << std::endl;
}
}
log.close();
delete[] data;
std::cout << "
Press any key and enter to quit." << std::endl;
char q;
std::cin >> q;
return 0;
}
如果你看一下模式,就会发生什么变得非常明显。
让我们看看生成的文件中的前几个迭代,这里为此进行了简化。
// Values are represented in hex
// For field member PCP: remember that 3 bits can only hold a max value of 7
// 8-bits 8-bits 3-bits 1-bit 12-bits
// data[0] data[1] PCP DEI ID
0x00 0x00 0 0 0
0x01 0x00 1 0 0
0x02 0x00 2 0 0
0x03 0x00 3 0 0
0x04 0x00 4 0 0
0x05 0x00 5 0 0
0x06 0x00 6 0 0
0x07 0x00 7 0 0 // PCP at max value since 3 bits only has 2^3 digit combinations
0x08 0x00 0 1 0
0x09 0x00 1 1 0
0x0a 0x00 2 1 0
0x0b 0x00 3 1 0
0x0c 0x00 4 1 0
0x0d 0x00 5 1 0
0x0e 0x00 6 1 0
0x0f 0x00 7 1 0 // the next iteration is where the bit carries into ID
0x10 0x00 0 0 1
// And this pattern repeats through out until ID has max value.
你的比特场在记忆中发生的事情是,第一个字节或8 bits
正在消耗PCP
,DEI
以及4 bits
的第一个ID
,我认为这是你感到困惑的地方。正如SoronelHaetir
在他们的简短回答中所说的那样,如果你想让你的3
位域的值为{0,0,20}
,那么你需要将你的data array
分别设置为data[0] = 0x40
和data[1] = 0x01
。来自data[0]
的位会溢出到其他位域成员中,当该成员不再包含足够高的值而不是分配的位数可以支持时。
这基本上意味着PCP
有3
可用bits
,它的最大组合位数是2^3 = 8
所以PCP
可以存储来自[0,7]
的值。由于DEI
只有1 bit
这个作为一个单位bool标志,只能存储[0,1]
的值,最后ID
有12 bits
可用的第一个4
来自data[0]
,最后的8
都来自data[1]
,这给你2^12 = 4096
组合给出[0,4095]
范围内值的数字,以十六进制表示最大值FFF
。这可以在日志或结果文件中看到。
我还将显示您的data[]
阵列与bitfield
平行的对齐方式
First Byte | Second Byte
data[0] | data[1]
data[n]: ([0][0][0]) ([0])-([0][0][0][0] | [0][0][0][0]-[0][0][0][0])
|
PCP DEI ID |
bitfield: ([0][0][0]) ([0])-([0][0][0][0] | [0][0][0][0]-[0][0][0][0])
编辑
OP在对这个答案的评论中提到了这些陈述:
我没有得到“当数据[0]中的位在该成员不再包含足够高的值时溢出到其他位域成员”时,我看不到溢出发生的位置--Lior Sharon
和
另外根据你在答案底部的对齐我所做的应该工作,因为当ID为20时,只有data1的第二个字节被位域使用
我在这里尝试做的是显示data[0]
和data[1]
的位模式,其值为0x40
和0x01
Byte 1 Byte 2
data[0] = 0x40 data[1] = 0x01
[0][1][0][0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
这就是data
在你将它转换为你的bitfield结构之前的位模式。现在让我们在演员表之前查看所有0s
的位域,然后让我们看一下与位域成员相关的十六进制值以及它们可以存储的值。我已经说过PCP
可以存储来自[0-7]
的值,DEI
可以存储值[0,1]
,ID可以存储来自[0-4095]
的十进制值。您将十六进制值分配给两个字节或16位内存。你希望PCP
和DEI
的值为0
,而ID
的值为20
(十进制)。你认为第一个字节的0x00
将给PCP
和DEI
0
的值,而0x14
应该给ID
20
的值。那样不行。 0x14
为十六进制值表示内存中的一个字节,但ID
有12 bits
或1.5 bytes
来存储。如果您参考上面的图表,成员PCP
只有3位存储,所以如果我们将7
的值添加到data[0]
中,PCP
将如下所示:[1][1][1]
in binary。即使没有使用data[1]
的字节,我们也可以将值推送到
以上是关于C ++ struct bit字段无法正确解析数据的主要内容,如果未能解决你的问题,请参考以下文章
线程“主”org.apache.spark.sql.AnalysisException 中的异常:由于数据类型不匹配,无法解析“named_struct()”: