C++ C++;结构位字段不为';无法正确解析数据

C++ C++;结构位字段不为';无法正确解析数据,c++,struct,network-programming,bit-fields,C++,Struct,Network Programming,Bit Fields,我正在尝试使用压缩结构从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]

我正在尝试使用压缩结构从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;
显然,我们看到vlan id是320而不是20,这不是我的意思。我假设问题是endian(我的机器是little endian),我不知道如何优雅地解决这个问题


可能位字段不是作业的正确工具?

您的id值是0x140而不是0x14,请记住位字段部分打包到类型中。您有16位可用。 如果希望它是0x14,则需要

data[0] = 0x40;
data[1] = 1;
OP问:

我假设问题是endian(我的机器是little endian),我不知道如何优雅地解决这个问题

也许位字段不是适合该作业的工具

尽管在处理位字段或联合时,考虑机器的端位始终是一个很好的考虑因素,而且不应该忘记。然而,在你目前的情况下,我不认为endian是任何问题的原因或关注点。至于问题的第二部分,则视乎具体需要而定。如果要写入的代码专门针对特定的体系结构/操作系统/平台,并且不可能是可移植的,那么如果正确构造了位字段,那么使用位字段应该没有问题。即使您决定移植到其他机器,您仍然可以使用位字段,但您必须更加小心,并且可能必须使用预处理器指令或控制开关&case语句编写更多代码,以使代码在一台机器上使用,并在另一台机器上做一件事

在使用位字段时,我认为混合类型时要考虑endian

struct Bitfield {
    unsigned a : 10,
             b : 10,
             c : 16;
    int      x : 10,
             y : 10,
             z : 16;
};
像上面这样的事情可能需要考虑endian


通过查看您的位域结构,我看到的是对位域内位对齐与结构本身对齐的误解

当前结构为:

您正在将对齐方式打包为尽可能小的
1字节
,因此此结构中的边界对于每个边界为
8位
。这没什么大不了的,也不言自明。然后使用一种类型的
uint16\u t
,它是
typedef
unsigned short
,大小为
2字节或
16位。
无符号短
的值范围为
[065535]

然后在结构中,您将位字段成员
PCP
DEI
ID
分别设置为具有位数:
3
1
12
。我向结构添加了注释以显示此模式

现在,正如在主函数中声明指向
uint8\u t
类型的指针一样,您可以创建上述结构的实例,然后为指针创建数组大小为
[2]
的动态内存。这里的
uint8\u t
是一个
typedef
用于
无符号字符的
typedef
,该字符的大小为
1字节或
8位
,由于您有
2位
,因此总共有
2字节或
16位
。好的,所以内存的总大小在
位字段结构和
数据[]
数组之间匹配

然后通过索引并使用十六进制值设置指针数组来填充指针数组。然后通过将
数组中的值转换为该类型,将
数组中的值分配给
位字段。然而,我认为您假设的是
data[0]
应该适用于位字段的第一个
2
成员,而
data[1]
应该适用于最后一个
ID
值。但情况并非如此:

这里发生的是,在代码的这一部分:

上面所说的并不是你认为它应该做的


我将制作一个图表,只是给你们展示一些例子:但是它太大了,无法在这里显示;所以我能做的就是提供一点代码在你的机器上运行,生成一个日志文件供你查看模式

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

内存中位字段的情况是,第一个字节或
8位
同时消耗
PCP
DEI
以及
ID
的第一个
4位
,我想这就是您感到困惑的地方。正如
SoronelHaetir
在其简短回答中所述,如果您希望
3
位字段的值为十进制
{0,0,20}
,则需要将
数据数组设置为
数据[0]=0x40
数据[1]=0x01
。当
数据[0]
中的位不能再包含比分配的位量所能支持的足够高的值时,该位将溢出到其他位字段成员中

这基本上意味着
PCP
具有
3
可用
,其最大组合位数为
2^3=8
,因此
PCP
可以存储
[0,7]
中的值。由于
DEI
只有
1位
,这就充当了一个单位布尔标志,它只能存储
[0,1]
的值,最后
ID
12位
可用,其中第一个
4
来自
数据[0]
,最后一个
8
都来自
数据[1]
这将为您提供组合数字
2^12=4096
这些数字的值范围为
[04095]
,十六进制中的最大值为
FFF
。这些都可以在日志或结果文件中看到


我还将显示
数据[]
数组与
的对齐方式
#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)
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. 
                       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])
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] 
  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.
#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;
}