使用工会的这种类型别名会引起未定义的行为吗?

时间:2019-03-12 03:57:55

标签: c++ language-lawyer

例如,

#include <cstdint>
#include <cstdio>

struct ipv4addr {
  union {
    std::uint32_t value;
    std::uint8_t parts[4];
  };
};

int main() {
  ipv4addr addr;
  addr.value = static_cast<std::uint32_t>(-1);
  std::printf("%hhu.%hhu.%hhu.%hhu",
    addr.parts[0], addr.parts[1], addr.parts[2], addr.parts[3]);
}  

cppref

  

该分配的详细信息是实现定义的,   从工会成员那里读取的不确定行为   最近写的。

因此,看起来代码调用了未定义的行为。但是页面上还显示

  

如果两个联合成员是标准布局类型,则可以明确定义为   在任何编译器上检查它们的公共子序列。

我不太明白这一点。是否使代码行为定义明确?

还要注意cppref在type aliasing上的描述。

  

每当尝试读取或修改存储的值时,   类型为DynamicType的对象通过类型为AliasedType的glvalue,   除非满足以下条件之一,否则行为是不确定的:

     
      
  • [...]
  •   
  • AliasedTypestd::bytecharunsigned char:这允许将任何对象的对象表示形式检查为   字节。
  •   

我想这也适用于std::uint8_t。不?

3 个答案:

答案 0 :(得分:2)

常见的初始子序列有一个荒谬的特定定义。 intstruct foo{int x;} 具有共同的初始子序列。

struct foo{int x;}struct bar{int y;}具有共同的初始子序列。

通过无关类型读取内存与从并集替代读取不同。该文本在那里没有任何作用。

您可以执行(std::unit8_t const*)&addr.value并将其视为4字节数组,前提是您的平台具有unit8_t。您获得的字节值是实现定义的。

但是,根据标准,您不能从parts[i]中读取(当值存在时)。

当标准声明未在标准下定义行为时,编译器可以自由指定行为,除非在编译期constexpr评估期间。

答案 1 :(得分:0)

读取最近未分配的工会成员的内容确实是未定义的行为。但是,我使用过的大多数编译器都具有非标准扩展名来允许它。

从技术上讲,如果您希望此操作安全无虞,则应将ip存储为int32_t,并通过reinterpret_cast重新解释以读取单个字节,如下所示:

int32_t ip = 185734;
int8_t *ip_bytes = reinterpret_cast<int8_t*>(&ip).

ip_bytes[etc]...

但是,您应该记住平台的字节顺序会影响任何32位读/写的字节顺序。因此,完全废弃int32_t的想法可能更安全,而只需使用字节数组即可。这完全取决于您的需求和/或您可能正在使用的任何库。

答案 2 :(得分:-2)

定义明确,可以完全执行fwrite,然后执行四个get调用以将其读回。实际结果取决于机器。在CPU偏爱的任何存储模式下,“获取此uint32的字节”。