C++ 结构位字段不能正确解析数据
C++ struct bit field doesn't parse data correctly
我正在尝试使用打包结构从 VLAN Header 中提取字段:
我创建了这个结构:
#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,这不是我的意思。我假设问题是字节顺序(我的机器是小端),我不知道如何优雅地解决这个问题。
也许位域不是这项工作的正确工具?
您的 id 值是 0x140 而不是 0x14,请记住位字段部分被打包到类型中。您有 16 位可用。
如果你想要它是 0x14 你需要
data[0] = 0x40;
data[1] = 1;
OP 问这个:
I assume the problem is endianness (my machine is little endian) and I have no idea how to resolve the problem elegantly.
Maybe bit fields isn't the right tool for the job?
尽管在使用位域或联合时,考虑机器的字节序始终是一个很好的考虑因素,也是不应忘记的事情。但是,在您目前的情况下,我看不到字节序在哪里是任何问题的原因或问题。至于问题的第二部分,就看具体需求了。如果要写入的代码专门用于特定 architecture/os/platform 并且不太可能是可移植的,那么如果正确构造,使用位域应该没有错。即使您决定移植到其他机器,您仍然可以使用位域,但您必须更加小心,并且可能必须使用预处理器指令或控制开关和案例语句编写更多代码才能使用代码并做一件事在一台机器上而不是另一台机器上。
当使用位域时,我认为在混合类型时会考虑字节序。
struct Bitfield {
unsigned a : 10,
b : 10,
c : 16;
int x : 10,
y : 10,
z : 16;
};
类似上面的内容可能需要考虑字节序。
通过查看您的位域结构,我看到的是对位域内位对齐与结构本身对齐的误解。
您当前的结构是:
#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
for unsigned short
的 2 bytes
大小或 16 bits
的类型。 unsigned short
的值范围为 [0,65535]
。
然后在结构中将位域成员 PCP
、DEI
和 ID
设置为具有位数:3
、1
、 12
分别。我在您的结构中添加了注释以显示此模式。
现在,在您的主函数中,您声明了一个指向 uint8_t
类型的指针,然后您创建了上述结构的实例,然后为您的指针创建动态内存,数组大小为 [2]
。这里 uint8_t
是 typedef
对 unsigned char
的 unsigned char
大小 1 byte
或 8 bits
一起工作,因为你有 2
总共有 2 bytes
或 16 bits
。好的,所以内存的总大小在 bitfield struct
和 data[]
数组之间匹配。
然后您通过索引并使用十六进制值设置它们来填充您的指针数组。然后,通过将 array
中的值强制转换为该类型,将其分配给 bitfield
。但是,我认为您假设的是 data[0]
应该适用于位域的第 1st 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 << "\nPress 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.
您的位域在内存中发生的情况是第 1st 字节或 8 bits
消耗了 PCP
、DEI
As Well As Well As the 1st 4 bits
of 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
这作为一个单比特布尔标志,它只能存储 [0,1]
的值,最后 ID
有 12 bits
可用,其中 1st 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 在对此答案的评论中提到了这些陈述:
I don't get the "The bits from data[0] are overflowing into the other bitfield members when that member can no longer contain a high enough value", I don't see where the overflow is occurring – Lior Sharon
和
also according to your alignment at the bottom of the answer what I did should work because only the second byte of the data1 is used by the bitfield when the ID is 20
我在这里尝试做的是用值 0x40
和 0x01
显示 data[0]
和 data[1]
的位模式
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
的位模式在将其转换为位域结构之前的样子。现在让我们看看转换前所有 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]
二进制。即使不使用 data[1]
的字节,我们也可以将值推送到 DEI
和 ID
成员中。
Byte1 = Byte 2 =
============================|==========================
PCP DEI ID
0x00 0x00
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x01 0x00
[0][0][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x02 0x00
[0][1][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x03 0x00
[0][1][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x04 0x00
[1][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x05 | 0x00
[1][0][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x06 0x00
[1][1][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x07 0x00
[1][1][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x08 0x00
[0][0][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x09 0x00
[0][0][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0A 0x00
[0][1][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0B 0x00
[0][1][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0C 0x00
[1][0][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0D 0x00
[1][0][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0E 0x00
[1][1][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0F 0x00
[1][1][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
// When we increment the hex value from 0x0F to 0x10 with a decimal value of 16
// this is where the overflow into the ID member happens and as of right now
// PCP has a max value of 7 and DEI has a max value of 1 where all bits are full.
// Watch what happens on the next iteration. Also note that we never gave any values
// to data[1] or byte 2 we only gave values to byte 1. This next value will
// populate a result into the bitfield's member ID.
0x10 0x00
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
// then for the next iteration it'll be like this and so on...
0x11 0x00
[0][0][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
0x12 0x00
[0][1][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
// while this pattern continues we seen that `0x10` gave us a bit at the right end
// of member ID so lets look at values 0x20, 0x30 & 0x40 in the first byte
// if 0x10 =
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
// then 0x20 should be
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][1][0]
// and 0x30 should be
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][1][1]
// finally 0x40 should be
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][1][0][0]
// This is all without touching byte.
// Remember we want both PCP & DEI to have values of 0 but we
// need a value of 0x16 or 20 in decimal in ID. Because of this
// overflow of bits due to the nature of bit fields, we can not
// just set the bytes directly with regular hex values as normal
// because member PCP only has 3 bits, member DEI has only 1, and
// the rest belong to ID. In order to get to the value we want
// we would have to iterate 0x40 all the way up to 0xFF before we would
// ever use byte 2 making it have a value of 0x01
// Another words: 0xFF 0x00 comes before 0x00 0x01 in this sequence
// bit patterns, but since we have the value of 0x40 already in the first
// byte of data[n] giving us a bit pattern of
[0][0][0] [0] [0][0][0] | [0][0][0][0] [0][1][0][0]
// what does making byte 2 with a value of 0x01 do to this pattern?
// It does this:
[0][0][0] [0] [0][0][0] | [0][0][0][1] [0][1][0][0]
// Okay so data[0] = 0x40 and data[1] = 0x01 so how does this
// give us the values of {0,0,20} or {0x00,0x00,0x14} ?
// Let's see from the far left going right the first 3 bits
// are PCP and all bits are 0 giving it a value of 0
// Next is the single bit for DEI which has a value of 0.
// Finally the next 12 bits are for ID and when we look at this 12 bit
// pattern we have [0][0][0][0] | [0][0][0][1] [0][1][0][0]
// Let's ignore the left 4 since they are all 0s or padding at this moment
// So we can see that [0][0][0][1] [0][1][0][0] = 0x14 in hex with a
// a decimal value of 20.
现在唯一的问题是:我在 MS Visual Studio 2017 CE 中使用英特尔四核处理器 运行ning Win7 x64 家庭高级版执行此操作,并将其编译为 x86 应用程序。位的实际存储位置因编译器、OS 和体系结构而异。我刚刚展示了从左到右的纯数学位表示,其中大多数机器将以从右到左的顺序存储它们的位。如果您 运行 在小端机器上使用 Visual Studio 编译器,您应该会得到类似的结果。
这里有一些关于位域的写得很好的文章;如果我遇到更多,我会 post 他们在这里:
这样的事情怎么样,使用 std::bitset
:
#include <iostream>
#include <bitset>
class VLANHeader
{
private:
// 000 0 000000000000
// PCP DEI ID
std::bitset<16> bin;
public:
VLANHeader(uint8_t byte1, uint8_t byte2) : bin(byte1 << 8 | byte2) {}
unsigned long getPCP() const { return (bin >> 13).to_ulong(); }
unsigned long getDEI() const { return ((bin >> 12) & std::bitset<16>(0x1)).to_ulong(); }
unsigned long getID() const { return (bin & std::bitset<16>(0xFFF)).to_ulong(); }
};
int main()
{
VLANHeader vh(0x00, 0x14);
std::cout << "PCP: " << vh.getPCP() << std::endl;
std::cout << "DEI: " << vh.getDEI() << std::endl;
std::cout << "ID: " << vh.getID() << std::endl;
system("pause");
return 0;
}
0x00
,0x14
这对字节会转为二进制为0b0000000000010100
,所以如果header格式是uint16_t PCP : 3, DEI : 1, ID : 12;
这就是我们要的PCP= > 000,DEI => 0,ID => 000000010100。这可以按照上面的代码通过掩码和移位来提取。甚至可能适用于不同的字节序系统。
我正在尝试使用打包结构从 VLAN Header 中提取字段:
我创建了这个结构:
#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,这不是我的意思。我假设问题是字节顺序(我的机器是小端),我不知道如何优雅地解决这个问题。
也许位域不是这项工作的正确工具?
您的 id 值是 0x140 而不是 0x14,请记住位字段部分被打包到类型中。您有 16 位可用。 如果你想要它是 0x14 你需要
data[0] = 0x40;
data[1] = 1;
OP 问这个:
I assume the problem is endianness (my machine is little endian) and I have no idea how to resolve the problem elegantly.
Maybe bit fields isn't the right tool for the job?
尽管在使用位域或联合时,考虑机器的字节序始终是一个很好的考虑因素,也是不应忘记的事情。但是,在您目前的情况下,我看不到字节序在哪里是任何问题的原因或问题。至于问题的第二部分,就看具体需求了。如果要写入的代码专门用于特定 architecture/os/platform 并且不太可能是可移植的,那么如果正确构造,使用位域应该没有错。即使您决定移植到其他机器,您仍然可以使用位域,但您必须更加小心,并且可能必须使用预处理器指令或控制开关和案例语句编写更多代码才能使用代码并做一件事在一台机器上而不是另一台机器上。
当使用位域时,我认为在混合类型时会考虑字节序。
struct Bitfield {
unsigned a : 10,
b : 10,
c : 16;
int x : 10,
y : 10,
z : 16;
};
类似上面的内容可能需要考虑字节序。
通过查看您的位域结构,我看到的是对位域内位对齐与结构本身对齐的误解。
您当前的结构是:
#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
for unsigned short
的 2 bytes
大小或 16 bits
的类型。 unsigned short
的值范围为 [0,65535]
。
然后在结构中将位域成员 PCP
、DEI
和 ID
设置为具有位数:3
、1
、 12
分别。我在您的结构中添加了注释以显示此模式。
现在,在您的主函数中,您声明了一个指向 uint8_t
类型的指针,然后您创建了上述结构的实例,然后为您的指针创建动态内存,数组大小为 [2]
。这里 uint8_t
是 typedef
对 unsigned char
的 unsigned char
大小 1 byte
或 8 bits
一起工作,因为你有 2
总共有 2 bytes
或 16 bits
。好的,所以内存的总大小在 bitfield struct
和 data[]
数组之间匹配。
然后您通过索引并使用十六进制值设置它们来填充您的指针数组。然后,通过将 array
中的值强制转换为该类型,将其分配给 bitfield
。但是,我认为您假设的是 data[0]
应该适用于位域的第 1st 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 << "\nPress 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.
您的位域在内存中发生的情况是第 1st 字节或 8 bits
消耗了 PCP
、DEI
As Well As Well As the 1st 4 bits
of 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
这作为一个单比特布尔标志,它只能存储 [0,1]
的值,最后 ID
有 12 bits
可用,其中 1st 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 在对此答案的评论中提到了这些陈述:
I don't get the "The bits from data[0] are overflowing into the other bitfield members when that member can no longer contain a high enough value", I don't see where the overflow is occurring – Lior Sharon
和
also according to your alignment at the bottom of the answer what I did should work because only the second byte of the data1 is used by the bitfield when the ID is 20
我在这里尝试做的是用值 0x40
和 0x01
data[0]
和 data[1]
的位模式
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
的位模式在将其转换为位域结构之前的样子。现在让我们看看转换前所有 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]
二进制。即使不使用 data[1]
的字节,我们也可以将值推送到 DEI
和 ID
成员中。
Byte1 = Byte 2 =
============================|==========================
PCP DEI ID
0x00 0x00
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x01 0x00
[0][0][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x02 0x00
[0][1][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x03 0x00
[0][1][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x04 0x00
[1][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x05 | 0x00
[1][0][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x06 0x00
[1][1][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x07 0x00
[1][1][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x08 0x00
[0][0][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x09 0x00
[0][0][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0A 0x00
[0][1][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0B 0x00
[0][1][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0C 0x00
[1][0][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0D 0x00
[1][0][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0E 0x00
[1][1][0] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
0x0F 0x00
[1][1][1] [1] [0][0][0][0] | [0][0][0][0] [0][0][0][0]
// When we increment the hex value from 0x0F to 0x10 with a decimal value of 16
// this is where the overflow into the ID member happens and as of right now
// PCP has a max value of 7 and DEI has a max value of 1 where all bits are full.
// Watch what happens on the next iteration. Also note that we never gave any values
// to data[1] or byte 2 we only gave values to byte 1. This next value will
// populate a result into the bitfield's member ID.
0x10 0x00
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
// then for the next iteration it'll be like this and so on...
0x11 0x00
[0][0][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
0x12 0x00
[0][1][1] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
// while this pattern continues we seen that `0x10` gave us a bit at the right end
// of member ID so lets look at values 0x20, 0x30 & 0x40 in the first byte
// if 0x10 =
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][0][1]
// then 0x20 should be
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][1][0]
// and 0x30 should be
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][0][1][1]
// finally 0x40 should be
[0][0][0] [0] [0][0][0][0] | [0][0][0][0] [0][1][0][0]
// This is all without touching byte.
// Remember we want both PCP & DEI to have values of 0 but we
// need a value of 0x16 or 20 in decimal in ID. Because of this
// overflow of bits due to the nature of bit fields, we can not
// just set the bytes directly with regular hex values as normal
// because member PCP only has 3 bits, member DEI has only 1, and
// the rest belong to ID. In order to get to the value we want
// we would have to iterate 0x40 all the way up to 0xFF before we would
// ever use byte 2 making it have a value of 0x01
// Another words: 0xFF 0x00 comes before 0x00 0x01 in this sequence
// bit patterns, but since we have the value of 0x40 already in the first
// byte of data[n] giving us a bit pattern of
[0][0][0] [0] [0][0][0] | [0][0][0][0] [0][1][0][0]
// what does making byte 2 with a value of 0x01 do to this pattern?
// It does this:
[0][0][0] [0] [0][0][0] | [0][0][0][1] [0][1][0][0]
// Okay so data[0] = 0x40 and data[1] = 0x01 so how does this
// give us the values of {0,0,20} or {0x00,0x00,0x14} ?
// Let's see from the far left going right the first 3 bits
// are PCP and all bits are 0 giving it a value of 0
// Next is the single bit for DEI which has a value of 0.
// Finally the next 12 bits are for ID and when we look at this 12 bit
// pattern we have [0][0][0][0] | [0][0][0][1] [0][1][0][0]
// Let's ignore the left 4 since they are all 0s or padding at this moment
// So we can see that [0][0][0][1] [0][1][0][0] = 0x14 in hex with a
// a decimal value of 20.
现在唯一的问题是:我在 MS Visual Studio 2017 CE 中使用英特尔四核处理器 运行ning Win7 x64 家庭高级版执行此操作,并将其编译为 x86 应用程序。位的实际存储位置因编译器、OS 和体系结构而异。我刚刚展示了从左到右的纯数学位表示,其中大多数机器将以从右到左的顺序存储它们的位。如果您 运行 在小端机器上使用 Visual Studio 编译器,您应该会得到类似的结果。
这里有一些关于位域的写得很好的文章;如果我遇到更多,我会 post 他们在这里:
这样的事情怎么样,使用 std::bitset
:
#include <iostream>
#include <bitset>
class VLANHeader
{
private:
// 000 0 000000000000
// PCP DEI ID
std::bitset<16> bin;
public:
VLANHeader(uint8_t byte1, uint8_t byte2) : bin(byte1 << 8 | byte2) {}
unsigned long getPCP() const { return (bin >> 13).to_ulong(); }
unsigned long getDEI() const { return ((bin >> 12) & std::bitset<16>(0x1)).to_ulong(); }
unsigned long getID() const { return (bin & std::bitset<16>(0xFFF)).to_ulong(); }
};
int main()
{
VLANHeader vh(0x00, 0x14);
std::cout << "PCP: " << vh.getPCP() << std::endl;
std::cout << "DEI: " << vh.getDEI() << std::endl;
std::cout << "ID: " << vh.getID() << std::endl;
system("pause");
return 0;
}
0x00
,0x14
这对字节会转为二进制为0b0000000000010100
,所以如果header格式是uint16_t PCP : 3, DEI : 1, ID : 12;
这就是我们要的PCP= > 000,DEI => 0,ID => 000000010100。这可以按照上面的代码通过掩码和移位来提取。甚至可能适用于不同的字节序系统。