C++ 中的简单存储类和严格的别名

Posted

技术标签:

【中文标题】C++ 中的简单存储类和严格的别名【英文标题】:Simple storage class in C++ and strict aliasing 【发布时间】:2010-11-13 00:16:24 【问题描述】:

我有以下代码用于存储一个小类。

#include <iostream>

template<typename T>
class storage

private:
  struct destroy
  
    T& m_t;
    destroy(T& t) : m_t(t)  
    ~destroy()  m_t.~T(); 
  ;

  char m_c[sizeof(T)];
  void* address()  return &m_c[0]; 

public:
  void set(const T& t)  new (address()) T(t); 

  T get()
  
    T& t = *static_cast<T*>(address());
    destroy _d(t);
    return t;
  

;

template<typename T>
class choosable_storage

private:
  union
  
    T*         m_p;
    storage<T> m_storage;
  ;
  bool m_direct;

public:
  choosable_storage() : m_direct(false)  

  void set_direct(const T& t)
  
    m_direct = true;
    m_storage.set(t);
  

  void set_indirect(T* const t)  m_p = t; 

  T get()
  
    if (m_direct) return m_storage.get();
    return *m_p;
  

;

int main(void)

  storage<int> s; // no problems
  s.set(42);
  std::cout << s.get() << std::endl;

  int i = 10;

  choosable_storage<int> c1; // strict aliasing warnings
  c1.set_indirect(&i);
  std::cout << c1.get() << std::endl;

  choosable_storage<int> c2;
  c2.set_direct(i);
  std::cout << c2.get() << std::endl;

  return 0;

gcc 4.4 警告我在返回时违反了storage::get() 中的严格别名规则。

AFAIK,我没有违反任何规则。我真的违反了严格的别名还是 gcc 在这里变得很挑剔?

有没有办法在不禁用严格别名的情况下让它免费警告?

谢谢

编辑:

另一方面,以下实现不会给出任何警告:

template<typename T>
class storage

private:
  struct destroy
  
    T& m_t;
    destroy(T& t) : m_t(t)  
    ~destroy()  m_t.~T(); 
    T const& operator()() const  return m_t; 
  ;

  char m_c[sizeof(T)];

public:
  void set(const T& t)  new(static_cast<void*>(m_c)) T(t); 

  T get(void)  return destroy(*static_cast<T*>(static_cast<void*>(m_c)))(); 

;

编辑:

gcc 4.5 及更高版本不会发出警告 - 所以显然这只是对严格别名规则的误解或 gcc 4.4.x 中的错误

【问题讨论】:

storage 类模板的意义何在?对于简单的T,这似乎是一个过于复杂的替代品。 我无法使用 GCC 4.2.1 重现该问题。代码看起来也不错。 我认为这个构造是为了通过一种标准方法(set)启用对 T 的赋值。 T 甚至可能缺少赋值运算符。 整个事情看起来像是一种将 T 的存储同质化存储指向 T 的指针的方法。在 choosable_storage 上调用 get 的客户端理论上不需要知道或关心是否包含一个实例或指向其他地方实例的指针。也就是说,我无法轻易想象一个真正需要这种东西的场景。像这样的 Voodoo 会让我重新考虑它应该适合的其余代码的设计。 不幸的是,我需要 choosable_storage - 它用于类似 std::future 的类中,用作等待来自线程或其他库的对象的通用方式。重新设计不会消除这一点。 【参考方案1】:

我真的违反了严格的别名还是 gcc 在这里变得很挑剔?

严格别名规则有两种不同的解释:

通常的弱严格别名规则:(char/unsigned char 类型除外)你不能使用强制类型转换或联合来执行类型双关,你需要memcpy(或两个@ 987654324@ 访问);这样的代码确实不太合理,并且在 C(最新的、非常荒谬的 C 标准除外)和 C++ 中被明确禁止。 不寻常的强严格别名规则:您不能重用内存:一旦内存区域具有“动态类型”(什么?),它就不能与其他类型一起使用。所以你不能写一个分配器函数(malloc替代)C,除非它每次只调用malloc/free

强规则定义不明确,破坏了许多合理的代码,并且出于某种原因被 GCC 维护者选择(完全基于自欺欺人和循环论证——这真的很难看)。为了使 C++ 代码能够与强严格的别名优化一起工作,G++ 的维护者对典型的 C++ 代码添加了悲观化(基于更多的自欺欺人),以保持优化!

我不知道他们是否/何时意识到自己的错误,如有疑问,请禁用严格别名。无论如何,这是一个非常小的优化。

【讨论】:

【参考方案2】:

就本问题而言,严格的别名规则本质上是说,您不应该访问对象,除非通过其自身类型的指针/引用或对字符类型(char 或 unsigned char)的指针/引用)。

在您的代码中,您有一个 char 类型元素的数组 m_c,并且您试图通过 T 类型的引用来访问它。这是一个严格的别名违规。在一些更奇特的平台上,这可能会产生影响,例如,如果 m_c 没有正确对齐以容纳 T 类型的元素。

【讨论】:

实际上我正在尝试通过 void* 指针访问它 - 我认为这足以通知编译器我想要别名。 编译器不擅长阅读人类行为的细微提示和细微差别。把它想象成一个典型的男性。您的代码违反了 C 标准,有关严格别名的部分。编译器可以打印警告和/或生成围绕别名工作的代码,具体取决于其默认设置和命令行选项。它无法根据它认为你想要的进行调整。 请解释“访问对象”的含义。

以上是关于C++ 中的简单存储类和严格的别名的主要内容,如果未能解决你的问题,请参考以下文章

c++ 模板化抽象基类数组,不违反严格别名规则

C++ 严格别名:这不是 MSFT 示例 UB 吗? [复制]

-Werror=C 中的严格别名错误

c中的严格别名和数组

C++:方法中的字符串成员别名

小对象堆栈存储、严格别名规则和未定义行为