在C中格式化位字段内的联合

在C中格式化位字段内的联合,c,unions,bit-fields,C,Unions,Bit Fields,我有一个特殊的问题,我有一个16位的结构。在结构内部,在成员“x_pos”之后,根据某些外部标志,接下来的5位表示不同的内容 如果设置了外部标志,则它们是旋转和缩放参数(rotscale_param),否则上面的两位是水平和垂直翻转位 我试图用一个并集来表示这个结构,但是当我这么大化(属性)时,我希望看到2个字节,但结果是4个字节 我的代码: typedef unsigned short u16; struct attribute { u16 x_pos : 9; union

我有一个特殊的问题,我有一个16位的结构。在结构内部,在成员“x_pos”之后,根据某些外部标志,接下来的5位表示不同的内容

如果设置了外部标志,则它们是旋转和缩放参数(rotscale_param),否则上面的两位是水平和垂直翻转位

我试图用一个并集来表示这个结构,但是当我这么大化(属性)时,我希望看到2个字节,但结果是4个字节

我的代码:

typedef unsigned short u16;

struct attribute {
    u16 x_pos : 9;
    union {
        u16 rotscale_param : 5;
        struct {
            u16 unused : 3;
            u16 hflip : 1;
            u16 vflip : 1;
            };
    };
    u16 size : 2;
};
如果有帮助的话,我正在尝试为这个结构编写C代码:

OBJ Attribute 
  Bit   Expl.
  0-8   X-Coordinate           
  When Rotation/Scaling used:
    9-13  Rotation/Scaling Parameter Selection
  When Rotation/Scaling not used:
    9-11  Not used
    12    Horizontal Flip      
    13    Vertical Flip        
  14-15 OBJ Size
以上报价来源:

以下是一个潜在的解决方案:

typedef struct attr_flag_set {
    u16 x_pos : 9;
    u16 rotscale_param : 5;
    u16 size : 2;
};

typedef struct attr_flag_unset {
    u16 x_pos : 9;
    u16 unused : 3;
    u16 hflip : 1;
    u16 vflip : 1;
    u16 size : 2;
};

union attribute_1 {
    attr_flag_unset attr_unset;
    attr_flag_set attr_set;
};

但是,我不确定这是否是理想的解决方案。

要得到正确的包装并不容易。
此外,该标准仅允许在声明类型相同时进行位打包。
根据ISO/IEC 9899 C99,
6.7.2.1结构和联合规范

一个实现可以分配任何大的可寻址存储单元 足够容纳一个位字段。如果剩余的空间足够大,则需要一个位字段 结构中紧跟其后的另一位字段应被压缩 到同一单元的相邻位中。如果空间不足, 是否将不适合的位字段放入下一个单元或 实现定义了相邻单元的重叠。秩序 单元内位字段的分配(从高阶到低阶或 低阶到高阶)是实现定义的。路线 未指定可寻址存储单元

因此,当您在位域声明中插入位域结构时,这会打破规则并保留另一个字用于存储。
唯一的解决方法是将声明还原为两个不同结构的联合:

#pragma pack(1)
union attribute
{
    struct
    {
        u16 x_pos : 9;    //See the homogeneous series of declarations
        u16 rotscale_param : 5;
        u16 size : 2;
    } struct1;
    struct
    {
        u16 x_pos  : 9;    //See the homogeneous series of declarations here also
        u16 unused : 3;
        u16 hflip  : 1;
        u16 vflip  : 1;
        u16 size   : 2;
    } struct2;
};
这在C99-C11下对我有效。
别忘了指示编译器打包数据。正如Mah让我注意到的那样,在大多数编译器上使用pragma或在其他编译器上使用特定语法都可以实现这一点。在这种情况下,打包也可以依赖于那些边界线应用程序。
P.S.还考虑字节顺序是依赖于编译器的,但很大程度上取决于CPU的字节数,因此,在一个小字节机器上工作良好的代码可能会在一个大字节的机器上失败。
如果需要完全可移植的代码,最好的做法是使用掩码和移位。

如果您试图使用
hflip
vflip
创建位字段,我认为您打算执行以下操作:

struct attribute {
    u16 x_pos : 9;
    union {
        u16 rotscale_param : 5;
        struct {
            u16 unused : 3,
                hflip  : 1,
                vflip  : 1;
        } hvflip;
    };
    u16 size : 2;
};
实际使用
unused
hflip
vflip
从单个
无符号短字符创建位字段。如另一个答案所示,使用3-complete
无符号shorts
表示有问题的
5
位,完全忽略了位字段的优点/用途。只需1
无符号短字符
即可表示16位

下面是一个简单的例子:

#include <stdio.h>

typedef unsigned short u16;

struct attribute {
    u16 x_pos : 9;
    union {
        u16 rotscale_param : 5;
        struct {
            u16 unused : 3,
                hflip  : 1,
                vflip  : 1;
        } hvflip;
    };
    u16 size : 2;
};

void binprnpad (const unsigned long v, size_t sz);

int main (void) {

    struct attribute attr = { .x_pos = 0b101010101,
                              .rotscale_param = 0b10101, 
                              .size = 0b10 };

    printf ("\n attr.x_pos (%3hu)          : ", attr.x_pos);
    binprnpad (attr.x_pos, 9);

    printf ("\n attr.rotscale_param (%3hu) : ", attr.rotscale_param);
    binprnpad (attr.rotscale_param, 5);

    printf ("\n attr.hvflip.unused (%3hu)  : ", attr.hvflip.unused);
    binprnpad (attr.hvflip.unused, 3);
    printf ("\n attr.hvflip.hflip (%3hu)   : ", attr.hvflip.hflip);
    binprnpad (attr.hvflip.hflip, 1);
    printf ("\n attr.hvflip.vflip (%3hu)   : ", attr.hvflip.vflip);
    binprnpad (attr.hvflip.vflip, 1);

    printf ("\n attr.size (%3hu)           : ", attr.size);
    binprnpad (attr.size, 2);
    printf ("\n\n");

    return 0;
}

void binprnpad (const unsigned long v, size_t sz)
{
    if (!sz) { fprintf (stderr, "error: invalid sz.\n"); return; }
    if (!v)  { putchar ('0'); return; }

    while (sz--)
        putchar ((v >> sz & 1) ? '1' : '0');
}

如果您有任何问题,请告诉我。

您不能假设编译器会将您的位域结构打包到尽可能紧凑的空间中。检查你的编译器文档,看看是否有可能导致这种情况发生的选项,但假设它会自动发生是错误的(如果连你的编译器都允许的话)。@mah:union放在一边,你会说,假设结构中的9位字段和2位字段背对背打包在一起是合理的吗?可能是@500 InternalServerError的重复,这可能是你直觉上所期望的(当然这是一个合理的偏好),但我恐怕假设它会发生是不合理的。C位字段似乎从未得到编译器的喜爱,因为它们需要使它们在与硬件接口时变得有用(这通常是它们最需要的地方)。感谢您指出这一点。我将尝试在DevkitARMAre上找到我能找到的东西。是否有
#pragma
不是特定于编译器的情况?您的回答很有道理,谢谢。这也是我想到的一个潜在的“变通”解决方案(在一个联合体中使用两个结构),但我不确定如果我能够使最初的方法可行,它是否是首选方案。非常感谢您的启示@当然可以。谢谢你的观点。其他编译器可以使用不同的语法(即GCC使用
\uuuuu属性\uuuuu(打包)
)。但是,正如您已经概述的,这类事情与HW非常接近,因此始终要小心处理:)不客气,但正如我在回答中所说,当您更改类型时,编译器会停止填充内存,这是标准的,因此没有其他方法。当您使用并集跟随
u16
声明时,序列被中断,编译器移动到另一个字节/字/DWORD等。
$ ./bin/struct_nested_bin

 attr.x_pos (341)          : 101010101
 attr.rotscale_param ( 21) : 10101
 attr.hvflip.unused (  5)  : 101
 attr.hvflip.hflip (  0)   : 0
 attr.hvflip.vflip (  1)   : 1
 attr.size (  2)           : 10