繁体   English   中英

灵活的数组成员访问联合原始字节

[英]Flexible Array Member to access union raw bytes

在嵌入式平台上。

假设我正在通过串行线路从从设备接收字节,在该线路上数据正确地进行了序列化和反序列化,以确保数据的字节序和大小正确。

我真正想要实现的是具有可变大小的struct test ,以允许将来扩展数组成员。

  • 多平台应用程序上是否存在意外行为?
  • c标准中是否有禁止这种访问的部分?

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

struct test
{
    uint32_t a;
    uint32_t b;
    uint32_t c[];
};

union test1
{
    struct test A;
    uint8_t B[256];
};

int main(void)
{
    union test1 test2;

    for (uint32_t i=0; i<256; i++)
    {
        test2.B[i] = i;
    }

    for (size_t i=0; i<(sizeof(test2.B)/sizeof(uint32_t))-2; i++)
        printf("Test: 0x%08X\n", test2.A.c[i]);
}

要考虑的一些事情:

  • 对准。 您不能便携地假设结构或联合将没有填充字节。 从理论上讲,某些与int大小有关的对齐要求不清楚的系统可能会在结构内部填充字节。

    由于这种情况大部分是理论上的,因此您可以通过添加来确保不会发生这种情况

     _Static_assert(sizeof(struct test) == sizeof(int)+sizeof(int), "Padding detected!"); 
  • 正如问题中提到的,Endianess是一个真正的问题,必须在某个地方进行处理。

  • 在联合中,或者在嵌入式系统中的其他任何地方,Signed int对此都没有任何意义。 这些可能会以多种方式造成破坏,尽管不会发布任何代码。 应该用stdint.h中确定性大小和签名的类型替换它们。
  • 键入punning。 尽管实现定义,但punning类型很好。 我不明白为什么您的代码会在常规的二进制补码系统上引起问题。 但是,从理论上讲,您可能会遇到不使用二进制补码而实现填充位,陷阱位等的外来系统的可移植性问题。我不会过分担心这种几乎不存在的系统的可移植性。
  • C标准。 您显然将无法将灵活的阵列成员代码移植到C90系统。 它可能在此处编译,但会调用未定义的行为。 此外,标准委员会的某些延迟部门在C11中将stdint.h可选。 我不会担心的。

总的来说,只要您处理某个地方的问题并摆脱int ,我的代码就可以很好地移植到所有有用的系统中。

在C89和C99的原始发行版中,写一个union成员并从另一个union成员读取具有实现定义的行为。 在TC1至C99中,将其更改为未指定的行为。 无论哪种方式,实际含义都是相同的:您可以写信给工会的一个成员,然后从另一个成员读回,而不必担心恶魔从您的鼻子中飞出来。 该标准并没有告诉您您将获得什么价值 ,但是有了实施知识,它应该是可以预测的。

话虽这么说,您很可能在struct test填充问题,字节序不一致等问题。 通过使用stdint.h固定宽度类型而不是int ,并最大程度地使用无符号类型,可以缓解其中的某些问题。 我也强烈建议您根据外部协议所处的明确字节序编写一个显式转换函数,例如

static int32_t
be32_to_cpu(const unsigned char *p)
{
    uint32_t x = 0;
    x |= ((uint32_t)p[0]) << 24;
    x |= ((uint32_t)p[1]) << 16;
    x |= ((uint32_t)p[2]) <<  8;
    x |= ((uint32_t)p[3]) <<  0;
    return (int32_t)x;
}

并从具有手动计算偏移量的unsigned char缓冲区中手动复制,例如

struct test
{
    int32_t a;
    int32_t b;
    int32_t c[62];
}

void convert_block(struct test *restrict out,
                   const unsigned char *restrict buf)
{
    out->a = be32_to_cpu(&buf[0]);
    out->b = be32_to_cpu(&buf[4]);
    for (int i = 0; i < 62; i++)
        out->c[i] = be32_to_cpu(&buf[4 * (i+2)]);
}

现代编译器将识别be32_to_cpu的惯用法并生成最佳代码。 对于小端,只需反转移位顺序即可。 请注意,必须将值汇编到无符号变量中,然后再转换为有符号,因为移入符号位具有未定义的行为。

如果您的有线协议发送大小可变的数据包,则大概有一个size字段,您将需要使用该字段来知道何时停止读取以及缓冲区的大小:

struct test
{
    uint32_t size;
    int32_t b;
    int32_t c[]; /* SIZE/4 - 2 values */
};

struct test *
read_block(int fd)
{
    char b1[4];
    if (read(fd, b1, 4) < 4) abort();
    uint32_t size = be32u_to_cpu(b1);

    char b2[size - 4];
    if (read(fd, b2, size - 4) < size - 4) abort();

    struct test *out = malloc(size);
    out->size = size;
    out->b = be32s_to_cpu(&b2[0]);
    for (int i = 0; i < size/4 - 2; i++)
        out->c[i] = be32s_to_cpu(&b2[(i+1)*4]);

    return out;
}

正确处理错误和简短的阅读内容作为练习。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM