【问题标题】:Is it undefined behaviour to read a different member than was written in a Union?读取与在联合中写入不同的成员是否是未定义的行为?
【发布时间】:2021-06-09 13:04:11
【问题描述】:
union test{
  char a; // 1 byte
  int b;  // 4 bytes
};

int main(){ 
  test t;
  t.a = 5;
  return t.b;
}

这个链接说:https://en.cppreference.com/w/cpp/language/union

从最近未写入的联合成员中读取是未定义的行为。

据此,我上面的示例代码有UB吗? 如果是这样,那么联盟的意义何在?我认为重点是读/写 不同的值类型形成相同的内存位置。

如果我需要访问 most recently written 值,那么我将只使用 常规变量而不是联合。

【问题讨论】:

  • 工会的最初动机是在您一次只需要一个成员时节省内存 afaik。那是在你没有大量记忆的过去。
  • 联合的真实用例:我们有一个 API 允许查询许多不同的参数。这些参数可以有不同的类型(包括类)。因此,我们返回一个包装结构,其中包含一个表示活动成员的枚举和一个可能的参数类型的联合,并填充了正确的数据。
  • 如果是这样,那么工会的意义何在? 使用工会并不意味着需要阅读其不活跃的成员。例如,请参阅 libstdc++ 中std::basic_string 的实现,其中短字符串的缓冲区使用容量成员变量进行别名。由于您可以通过将数据指针与此缓冲区的地址进行比较来检测短/长字符串,因此无需访问非活动成员并且联合仍然有用。代码:github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/….
  • 这能回答你的问题吗? Unions and type-punning
  • @463035818_is_not_a_number 这绝对不只是一些old-days-issue。联合仍在使用,例如,在所有主流库(libstdc++、libc++、Microsoft STL)中实现字符串类。您不希望字符串对象过大,因为内存中可能会同时存在很多对象。

标签: c++


【解决方案1】:

是的,该行为在 C++ 中未定义。

当您向工会成员写入值时,请考虑该成员成为活动成员。

读取非活动成员的任何联合成员的行为是未定义的。

在 C++ 中,union 通常与另一个变量相结合,用作识别活动成员的方法。

【讨论】:

  • 重要的是,UB 并不意味着“崩溃”。做 OP 认为 应该 直到有一天不是有效的 UB。
  • 是否也适用于 C 语言?还是只有 C++?
  • C++ 更严格。在 C 语言中,您确实可以通过 union 键入双关语。
  • 值得补充的是,一些实现(可能是大多数)支持将非活动联合成员读取为非标准扩展。 (例如 IIRC,libc++ 中的 SSO 实现依赖于此。)
  • @ChrisBD 两个成员分别需要 5 个,但由于填充/对齐,将它们放入 struct 或 class 需要 8 个。
【解决方案2】:

您认为拥有工会而无法读取其非活动成员会使它们无用的暗示是错误的。考虑以下字符串类的简化实现:

class string {
  char* data_;
  size_t size_;
  union {
    size_t capacity_;
    char buffer_[16];
  };

  string(const char* str) : size_(strlen(str)) {
    if (size_ < 16) 
      data_ = buffer_;  // short string, buffer_ will be active
    else {
      capacity_ = size_;  // long string, capacity_ is active
      data_ = new char[capacity_ + 1];
    }
    memcpy(data_, str, size_ + 1);      
  }

  bool is_short() const { return data_ == buffer_; }
  ...
public:
  size_t capacity() const { return is_short() ? 15 : capacity_; }
  const char* data() const { return data_; }
  ...
};

这里,如果存储的字符串少于 16 个字符,则存储在buffer_ 中,data_ 指向它。否则,data_ 指向动态分配的缓冲区。

因此,您可以通过比较 data_ 和 buffer_ 来区分这两种情况(短字符串/长字符串)。当字符串很短时,buffer_ 处于活动状态,您不需要读取capacity_,因为您知道它是 15。当字符串很长时,capacity_ 处于活动状态,您不需要读取 @ 987654330@,因为它没有被使用。

正是这种方法在 libstdc++ 中使用。那里有点复杂,因为std::string 只是std::basic_string 类模板的一个特化,但想法是一样的。源代码来自include/bits/basic_string.h:

enum { _S_local_capacity = 15 / sizeof(_CharT) };

union
{
  _CharT    _M_local_buf[_S_local_capacity + 1];
  size_type _M_allocated_capacity;
};

如果您的程序同时处理大量字符串(例如,数据库),它可以节省大量空间。如果没有联合,每个string 对象将在内存中多占用 8 个字节。

【讨论】:

    猜你喜欢
    • 2019-03-09
    • 1970-01-01
    • 1970-01-01
    • 2019-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-08
    • 1970-01-01
    相关资源
    最近更新 更多