有没有一种方法可以改变变量在内存中的存储方式(位数)?

3

假设我有以下数据结构(伪代码):

struct
{
  uint8  id;
  bool   failure;
  uint8  value;
}  

现在假设我希望以以下方式将数据存储在内存中: 位7-6:id 位5:故障 位4-0:值
在C/C++/Visual Studio中是否有任何方法可以实现这一点?我知道你可以在Ada中实现它,但那没什么用。
编辑:抱歉没有表达清楚,我确实需要特定的内存布局。此结构将用于通过串行通道发送的消息,并且需要符合接口规范。
5个回答

7
您可以使用C位域(bitfields):
struct
{
  uint8 id : 2;
  bool failure : 1;
  uint8 value : 5;
};

然而,尽管这样做可能会减少struct*使用的内存量,但这并不能保证任何特定的内存布局;每个字段分配的具体位将取决于您的编译器和/或平台ABI。如果您需要为特定字段分配特定的位索引,则需要手动打包和解包。或者,如果您的代码不需要可移植性,您可以查找如何编译器打包位字段,并相应地排序结构的成员。
*-C标准对位域的布局几乎没有限制,而C ++则更少。通常情况下,它将导致更少的内存使用,但如果编译器决定它将使用的最小位域分配单元是32位字段或其他内容,则大小实际上可能会增加。请参见ISO / IEC 9899:1999(E)§6.7.2.1 / 10

1
@Ben,如果你只使用字段中的一部分位,那也没关系。 - Blindy
3
在某些编译器上,改变类型会导致新的分配单元启动。 - Ben Voigt
1
@bdonlan:我之前的评论漏掉了一个关键字,但仍然能够添加它。操作词来自于[class.bit]部分:“类对象内位域的分配是实现定义的。位域的对齐是实现定义的。” - Ben Voigt
@Ben,这是来自C++标准吗?我在看C99 :) - bdonlan
@Ben Voigt:根据标准,常量表达式可能比位域类型的对象表示(3.9)中的位数更大;在这种情况下,额外的位被用作填充位,并且不参与位域的值表示(3.9)。这意味着,如果您使用uint16_t声明一个20位的位域,则效果是有4位仅用于填充,并且不参与值,即上面示例中的b仅包含16个有效位。位域声明中的类型确实很重要。 - David Rodríguez - dribeas
显示剩余11条评论

3
struct
{
  int  id     :2;
  bool failure:1;
  int  value  :5;
} 

虽然这满足条件,但结构本身仍将由编译器对齐。如果操作员需要节省空间,则应强制进行对齐。 - Chad
同意,只是想向楼主指出——通常使用位域的目的是为了节省空间,而严格地把它们放在结构中不会使结构的大小减小,这必须从对齐强制实现。 - Chad
听起来串口连接的设备想要接收一个字节。该字节的格式为,第7-6位是ID,第5位是故障指示器,第4-0位是某个值。 - dbasnett
@mike,我回答中的结构就是你需要的。 - Blindy
我投票支持这个答案,因为它在value的位数上是正确的,但数据类型都是错误的(问题要求无符号)。 - Ben Voigt
显示剩余2条评论

2

针对原始数据的答案,你需要将数据放在一个 uint8_t 变量中,并使用位掩码和位移操作来提取数据,以下是一个示例实现:

class MyData
{
private:
    uint8_t data;

public:
    MyData() : data(0) {}
    MyData(uint8_t id, bool failure, uint8_t value) : data(0)
    {
        Id(id);
        Failure(failure);
        Value(value);
    }

    uint8_t Id()
    {
        return (data>>6);
    }

    void Id(uint8_t id)
    {
        data &= 0x3F;
        data |= ((id&0x3)<<6);
    }

    bool Failure()
    {
        return (data & 0x20);
    }

    void Failure(bool failure)
    {
        if(failure)
        {
            data |= 0x20;
        }
        else
        {
            data &= 0xDF;
        }
    }

    uint8_t Value()
    {
        return (data & 0x0F);
    }

    void Value(uint8_t val)
    {
        data &= 0xF0;
        data|=(val&0xF);
    }

};

举例来说,假设您想设置Id的最高位为1和0,用二进制掩码表示为0xC0。当然,您必须将其向右移动6位以获取实际值,否则您可能会得到128而不是2。我去掉了掩码,因为实际上没有更高的位,位移操作会移除低位。
设置也类似,您需要覆盖先前的数据,这是指令一(ox3F==00111111b),and运算符清除掩码中为0的部分,并不触及其他位。指令2使用or运算符将(先前已清除的)高位设置为新值,并保留原始值,因为低位为0且a|0==a
其他部分也类似,希望这可以帮助您理解...。
总结一下,检索是“应用掩码和位移”,存储是“使用&清除位并使用|存储位”。当然,一旦您传递多字节值,事情就会变得更加复杂(您必须考虑字节序)。

0

这可以使用位域来实现。在这种特定情况下,C++代码如下:

struct
{
    uint8  value : 5; // bits 0-4
    bool   failure : 1; // bit 5
    uint8  id : 2; // bit 6-7
};

注意:一定要检查编译器文档,因为位序和内存填充可能会影响可移植性。有关更多信息,请参见http://en.wikipedia.org/wiki/Bit_field或在Google上搜索C++位字段。


0

有特定于平台的方法可以实现结构体打包。但如果你想遵循规范,就必须手动将其打包到 uint8 缓冲区或类似的缓冲区中。编译器会尊重其他提出的解决方案中字段的大小,但它可以以任何方式将它们打包在结构体内部。例如,为了进行速度优化,它可能在结构体字段之间放置未使用的填充。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接