如何在数组中打包结构并删除零填充?

Posted

技术标签:

【中文标题】如何在数组中打包结构并删除零填充?【英文标题】:How to pack structs in array and remove zero-padding? 【发布时间】:2021-12-25 09:22:01 【问题描述】:

据我了解,"there will never be padding in between elements of an array"。而且我知道结构必须至少有一个字节长,否则它将用零填充。

我想要一个结构数组,每个大小为 4 位,没有零填充。我可以对数组应用某种“包装”吗?

我希望我的输出为 0xFFFF (0b1111_1111_1111_1111),但我无法摆脱结构的填充。

#include <stdio.h>
#include <stdint.h>
#include <string.h>

int main() 

    struct data_struct 
        unsigned a: 1;
        unsigned b: 3;
     __attribute__((packed));  // avoid structure padding

    union 
        struct data_struct data[4];
        uint16_t data_uint;
     union_data;

    memset(&union_data.data_uint, 0, sizeof(union_data.data_uint));
    for (int i = 0; i < 4; ++i) 
        union_data.data[i].a = 1;
        union_data.data[i].b = 7;
    

    printf("union_data = 0x%04X\n", union_data.data_uint);  // 0x0F0F  == 0b0000_1111_0000_1111
    return 0;

【问题讨论】:

之前的评论建议使用 memcpy。但问题是我的结构小于一个字节,据我了解 memcpy 只复制了字节。 【参考方案1】:

我可以对数组应用某种“包装”吗?

不,没有。字节是最低的可寻址单元,它至少有 8 位 - 所以所有变量将至少对齐到 8 位,并且大小至少为 8 位。

如何在数组中打包结构并去除零填充?

不要。写入访问器函数并使用位操作来分配和检索数据。更喜欢编写可移植的代码。

也不喜欢使用位域 - 请注意,字节内的位域顺序(LSB 与 MSB)是实现定义的,位域之间的填充也是实现定义的。为了便于移植,请使用位操作编写访问器函数。

这个想法是struct data_struct data[4] 中的第二个和第四个元素将从字节边界的中间开始 - 这是不可能的。对于您的情况,如果您想以这种方式访问​​它们,则必须从正确对齐的结构内的打包联合中提取数据:

  union union_data_t 
        struct 
            unsigned char a1 : 1;
            unsigned char b1 : 3;
            unsigned char a2 : 1;
            unsigned char b2 : 3;
         data[2];
        uint16_t data_uint;
    union_data;
   struct mydata union_data_get(union union_data_t *t, unsigned idx) 
       struct mydata r;
       r.a = idx%2 ? t->data[idx/2].a2 : t->data[idx/2].a1;
       r.b = idx%2 ? t->data[idx/2].b2 : t->data[idx/2].b1;
       return r;
   
   void union_data_get(union union_data_t *t, unsigned idx, struct mydata mydata) 
       if (idx%2)  t->data[idx/2].a2 = mydata.a; 
       else  t->data[idx/2].a1 = mydata.a; 
       if (idx%2)  t->data[idx/2].b2 = mydata.b; 
       else  t->data[idx/2].b1 = mydata.b; 
   

听起来像是最好的 gcc 特定抽象,但现在无论如何都没有理由使用位域 - 访问器函数无论如何都可以使用位操作来编写:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

struct data_struct 
    unsigned a: 1;
    unsigned b: 3;
 __attribute__((packed));  // avoid structure padding

struct data_struct data_struct_array_get(unsigned char *t, unsigned idx) 
    const unsigned mask = 4 * (idx % 2);
    unsigned v = (t[idx/2] >> mask) & 0xf;
    return (struct data_struct)v>>3, v;

void data_struct_array_set(unsigned char *t, unsigned idx, struct data_struct data) 
    const unsigned v = data.a << 3 | data.b;
    const unsigned mask = 4 * (idx % 2);
    t[idx/2] &= ~(0xf << mask);
    t[idx/2] |= v << mask;
 

int main() 
    union union_data_t 
        unsigned char data[2];
        uint16_t data_uint;
     union_data;
    
    for (int i = 0; i < 4; ++i) 
        data_struct_array_set(union_data.data, i, 
            (struct data_struct)1, 7
        );
    

    printf("union_data = 0x%04X\n", union_data.data_uint);
    return 0;

【讨论】:

非常感谢。最后的代码 sn-p 非常完美,因为它可以轻松调整数组大小。 在你的第一个代码 sn-p 中,最后一个函数应该是 set 而不是 get。我需要更改 6 个字符,否则 *** 不允许我编辑您的帖子。 也许我说得太早了。例如,我无法设置 a:2b:3 的结构。即使使用const unsigned mask = 5 * (idx % 2); t[idx / 2] &amp;= ~(0x1F &lt;&lt; mask);,我认为这仅适用于写入4位,因为idx/2 这听起来像是一个很好的另一个问题 - 发布问题描述(将 5 位元素打包到 unsigned char 数组中),发布您的代码以及您的代码如何失败。当然,8 不是 5 的倍数——因此会有元素跨越字节边界,因此需要一次写入两个字节才能写入一个元素。而且,您还可以更抽象地解决这个问题,而不是创建一个 n 位元素数组,而是创建一个抽象 bit-stream,并一次流 5 次 1 位。

以上是关于如何在数组中打包结构并删除零填充?的主要内容,如果未能解决你的问题,请参考以下文章

如何在列表中分组并填充零

Elasticsearch如何修改Mapping结构并实现业务零停机

如何从数组中并行删除零值

如何从填充零的 3 维 numpy 数组创建 4 维 numpy 数组?

如何将零填充的多维数组传递给 C++ 中的函数?

如何用前导零填充数组?