【问题标题】:How to get around zero-sized arrays?如何绕过零大小的数组?
【发布时间】:2021-12-22 23:21:57
【问题描述】:

在我正在使用的图书馆中,我发现了这样的结构:

typedef struct {
    uint16_t id;
    uint16_t len;
    union {
        uint8_t data[0];
        uint16_t data16[0];
        uint32_t data32[0];
    }
} packet_t;

所以这是一个具有灵活长度数据的数据包,可以访问每个字长。

但标准规定对象的大小不能为零,因此编译它会引发警告(“ISO C 禁止零大小数组”)。

我在 gcc 中编译代码,因此这个零大小的数组 hack 将由于它的扩展而做得很好。但是有什么办法可以让我对此真正“迂腐”吗?由于联合,灵活数组成员没有帮助。

如果我只是扔掉多字节数据成员会很简单,但我想尽可能地保留它们,因为库中的某些函数依赖于这些。

起初我以为我可以改用一个大小的数组,但我不确定是否完全没有副作用。有什么建议吗?

【问题讨论】:

  • 我不明白你的问题。你想学究什么?你要解决什么问题?
  • @MarcusMüller 使用代码没有问题。我很好奇的是是否有更符合标准的方式来编写它。 (因为按照标准,大小为零的数组是非法的)
  • 您能否编辑您的问题以说明这一点?
  • 放弃联合,跟踪你存储的数据类型,然后使用uint8_t的灵活数组成员。
  • @Lundin 当然可以。这是图书馆的一个小告密者,它确实有一个序列化器:)

标签: arrays c language-lawyer flexible-array-member


【解决方案1】:

生成可以使用 -pedantic 标志编译的东西并非不可能。但我认为如果没有 C11,您将无法在语法方面获得完全相同的语义。

转换后的packet_t 可能看起来像这样

typedef union packet_t {
    struct { uint16_t id;   uint16_t len; };
    struct { uint16_t id8;  uint16_t len8;  uint8_t data[];    };
    struct { uint16_t id16; uint16_t len16; uint16_t data16[]; };
    struct { uint16_t id32; uint16_t len32; uint32_t data32[]; };
} packet_t;

需要匿名结构 (C11) 将字段嵌套为 packet_t 的成员。这就是使p->id 仍然有效的原因。其他 id*len* 字段是虚拟字段,因为灵活的数组成员可能不是结构的唯一成员。他们在那里根据联合的公共初始序列保证使访问格式良好。

看着它,我不禁觉得你最好完全放弃多个 data 字段。 uint8_t 灵活数组成员可以包含其他两个可能包含的任何内容。

【讨论】:

    【解决方案2】:

    在 C99 之前,没有符合标准的方式。在 C99 中,

    typedef struct {
        uint16_t id;
        uint16_t len;
        uint8_t data[];
    } packet_t;
    

    是做一个灵活长度结构的合法方式。

    无论如何,您的 union 几乎是无用的合成糖:无论如何您都需要知道外部的类型,而 C 不是类型限制的,所以无论您写 packet.data16[2] 还是 uint16_t* data = packet.data; data[2] 都没什么区别。

    没有符合标准的方法可以将具有不同成员长度的 union 类型的数组作为最后一个元素。

    【讨论】:

      【解决方案3】:

      所以这是一个具有灵活长度数据的数据包,可以访问每个字长。

      您不能在 C 中定义可变大小的数组(通常,在特殊情况下可以,但不常见)。该语言没有任何方法可以动态更改数组的大小,因为数组的长度与其声明相关联(必须在编译时知道)没有允许您指定的类型参数之类的东西声明此类变量时数组在结构中的元素数。

      解决方法带有指针。可以声明一个数组元素类型指针,并为其动态分配内存,如下:

      typedef struct {
          uint16_t id;
          uint16_t len;
          union {
              uint8_t *data;
              uint16_t *data16;
              uint32_t *data32;
          } payload;  /* you need to name the union field!!! */
      } packet_t;
      

      并通过调用malloc() 来初始化指针,提供适当的内存量。稍后,如果您改变主意,可以free() 指针并分配anocher(同样,使用malloc())和不同的大小。

          packet_t pkt;
          pkt.id = 23;
          pkt.len = 113;
          pkt.payload.data = malloc[pkt.len];
          ....
          /* access */
          printf("third 16bit data in the payload is: 0x%04x\n",
                  pkt.payload.data16[2]);
      

      【讨论】:

        【解决方案4】:

        如果您使用的是 C99 之前的编译器,那么一个简单的常用技巧就是将大小设置为 1。当然,在处理它时您还需要将大小加/减 1

        typedef struct {
            uint16_t id;
            uint16_t len;
            union {
                uint8_t data[1];
                uint16_t data16[1];
                uint32_t data32[1];
            }
        } packet_t;
        

        另见

        【讨论】:

        • 引入了灵活的数组成员,因为即使在 C90 中,诸如此类的脏代码(“struct hack”)始终是未定义的行为。因此,无论 C 版本如何,这始终是错误的解决方案。
        • 我什么时候会关心“加/减 1”? sizeof() 不是为零大小数组给出 0,而不是数据的实际长度吗?
        • @morimn sizeof 仅给出标题的大小加上 1 个元素,因此减去该元素大小以获得标题大小
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-11-12
        • 2015-08-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多