处理数据序列化而不违反严格别名规则

时间:2015-06-19 18:38:40

标签: c strict-aliasing

通常在嵌入式编程中(但不限于),需要序列化一些任意struct,以便通过某个通信通道发送它或写入某个内存。

示例

让我们考虑在N对齐的内存区域中由不同数据类型组成的结构:

struct
{
    float a;
    uint8_t b;
    uint32_t c;
} s; 

现在让我们假设我们有一个库函数

void write_to_eeprom(uint32_t *data, uint32_t len);

将指向数据的指针写为uint32_t*。现在我们想使用此函数将s写入eeprom。一种天真的方法是做类似

的事情
write_to_eeprom((uint32_t*)&s, sizeof(s)/4);

但这显然违反了严格的别名规则。

第二个例子

struct
{
    uint32_t a;
    uint8_t b;
    uint32_t c;
} s; 

在这种情况下,别名(uint32_t*)&s不违反规则,因为指针与指向第一个字段类型的指针兼容,这是合法的。但!可以实现库函数,使得它正在执行一些指针算法来迭代输入数据,而这个算术结果指针与它们指向的数据不兼容(例如data+1是{{1}类型的指针但是它可能指向uint8_t字段)。正如我所理解的那样,这再次违反了规则。

可能的解决方案?

将有问题的结构包装在一个带有所需类型数组的联合中:

uint32_t*

并将union { struct_type s; uint32_t array[sizeof(struct_type) / 4]; } u; 传递给库函数。

这是正确的方法吗?这是唯一正确的方法吗?还有什么其他办法?

2 个答案:

答案 0 :(得分:2)

只是一个注释我不完全确定,但将uint8_t*投射到char*here)并不总是安全的。

无论你的写函数的最后一个参数想要什么,要写的字节数还是uint32_t个元素的数量是多少?我们稍后假设,并假设您要编写结构的每个成员以分隔整数。你可以这样做:

uint32_t dest[4] = {0};
memcpy(buffer, &s.a, sizeof(float));
memcpy(buffer+1, &s.b, sizeof(uint8_t));
memcpy(buffer+2, &s.c, sizeof(uint32_t));

write_to_eeprom(buffer, 3 /* Nr of elements */);

如果要将结构元素连续复制到整数数组 - 可以先将结构成员连续复制到字节数组 - 然后将字节数组复制到uint32_t数组。并且还将字节数作为最后一个参数传递 - sizeof(float)+sizeof(uint8_t)+sizeof(uint32_t)

答案 1 :(得分:0)

考虑到写入eeprom通常比写入普通内存更慢,有时慢得多,使用中间缓冲区很少会影响性能。我意识到这与this comment相反,但我觉得值得考虑,因为它处理所有其他C问题

编写一个具有无对齐,别名或大小问题

的辅助函数
extern void write_to_eeprom(/* I'd expect const */ uint32_t *data, uint32_t len);

// Adjust N per system needs
#define BYTES_TO_EEPROM_N 16

void write_bytes_to_eeprom(const void *ptr, size_t size) {
  const unsigned char *byte_ptr = ptr;
  union {
    uint32_t data32[BYTES_TO_EEPROM_N / sizeof (uint32_t)];
    unsigned char data8[BYTES_TO_EEPROM_N];
  } u;

  while (size >= BYTES_TO_EEPROM_N) {
    memcpy(u.data8, byte_ptr, BYTES_TO_EEPROM_N);  // **
    byte_ptr += BYTES_TO_EEPROM_N;
    write_to_eeprom(u.data32, BYTES_TO_EEPROM_N / sizeof (uint32_t));
    size -= BYTES_TO_EEPROM_N;
  }

  if (size > 0) {
    memcpy(u.data8, byte_ptr, size);
    while (size % sizeof (uint32_t)) {
      u.data8[size++] = 0;  // zero fill
    }
    write_to_eeprom(u.data32, (uint32_t) size);
  }
}

// usage - very simple
write_bytes_to_eeprom(&s, sizeof s);

**可以使用memcpy(u.data32, byte_ptr, BYTES_TO_EEPROM_N);来处理@zwol issue