gcc:如何正确使用__attribute((__may_alias__))以避免"derefencing type-punned pointer"警告

gcc: How to use __attribute((__may_alias__)) properly to avoid "derefencing type-punned pointer" warning

本文关键字:警告 type-punned pointer derefencing may 何正确 gcc attribute alias      更新时间:2023-10-16

我有一些代码使用类型punning来避免必须调用成员"object"的构造函数和析构函数,除非/直到真正需要使用该对象。

它工作得很好,但在g++4.4.3下,我收到了可怕的编译器警告:

jaf@jeremy-desktop:~$ g++ -O3 -Wall puns.cpp 
puns.cpp: In instantiation of ‘Lightweight<Heavyweight>’:
puns.cpp:68:   instantiated from here 
puns.cpp:12: warning: ignoring attributes applied to ‘Heavyweight’ after definition
puns.cpp: In destructor ‘Lightweight<T>::~Lightweight() [with T = Heavyweight]’:
puns.cpp:68:   instantiated from here
puns.cpp:20: warning: dereferencing type-punned pointer will break strict-aliasing rules
puns.cpp: In member function ‘void Lightweight<T>::MethodThatGetsCalledRarely() [with T = Heavyweight]’:
puns.cpp:70:   instantiated from here
puns.cpp:36: warning: dereferencing type-punned pointer will break strict-aliasing rules

我的代码试图使用gcc的__attribute((__may_alias__))让gcc知道潜在的别名,但gcc似乎不明白我想告诉它什么。是我做错了什么,还是gcc 4.4.3只是在__may_alies__属性上有一些问题?

玩具代码再现编译器警告如下:

#include <stdio.h>
#include <memory>    // for placement new
#include <stdlib.h>  // for rand()
/** Templated class that I want to be quick to construct and destroy.
  * In particular, I don't want to have T's constructor called unless
  * I actually need it, and I also don't want to use dynamic allocation.
  **/
template<class T> class Lightweight
{
private:
   typedef T __attribute((__may_alias__)) T_may_alias;
public:
   Lightweight() : _isObjectConstructed(false) {/* empty */}
   ~Lightweight()
   {
      // call object's destructor, only if we ever constructed it
      if (_isObjectConstructed) (reinterpret_cast<T_may_alias *>(_optionalObject._buf))->~T_may_alias();
   }
   void MethodThatGetsCalledOften()
   {
      // Imagine some useful code here
   }
   void MethodThatGetsCalledRarely()
   {
      if (_isObjectConstructed == false)
      {
         // demand-construct the heavy object, since we actually need to use it now
         (void) new (reinterpret_cast<T_may_alias *>(_optionalObject._buf)) T();
         _isObjectConstructed = true;
      }
      (reinterpret_cast<T_may_alias *>(_optionalObject._buf))->DoSomething();
   }
private:
   union {
      char _buf[sizeof(T)];
      unsigned long long _thisIsOnlyHereToForceEightByteAlignment;
   } _optionalObject;
   bool _isObjectConstructed;
};
static int _iterationCounter = 0;
static int _heavyCounter     = 0;
/** Example of a class that takes (relatively) a lot of resources to construct or destroy. */
class Heavyweight
{
public:
   Heavyweight()
   {
      printf("Heavyweight constructor, this is an expensive call!n");
      _heavyCounter++;
   }
   void DoSomething() {/* Imagine some useful code here*/}
};
static void SomeMethod()
{
   _iterationCounter++;
   Lightweight<Heavyweight> obj;
   if ((rand()%1000) != 0) obj.MethodThatGetsCalledOften();
                      else obj.MethodThatGetsCalledRarely();
}
int main(int argc, char ** argv)
{
   for (int i=0; i<1000; i++) SomeMethod();
   printf("Heavyweight ctor was executed only %i times out of %i iterations, we avoid %.1f%% of the ctor calls!.n", _heavyCounter, _iterationCounter, 100.0f*(1.0f-(((float)_heavyCounter)/((float)_iterationCounter))));
   return 0;
}

我认为typedef混淆了GCC。当直接应用于变量定义时,这类属性似乎效果最好。

这个版本的课程适合我(GCC 4.6.0):

template<class T> class Lightweight
{
private:
  //  typedef T __attribute((__may_alias__)) T_may_alias;
public:
  Lightweight() : _isObjectConstructed(false) {/* empty */}
  ~Lightweight()
  {
    // call object's destructor, only if we ever constructed it
    if (_isObjectConstructed) {
      T * __attribute__((__may_alias__)) p
        = (reinterpret_cast<T *>(_optionalObject._buf));
      p->~T();
    }
  }
  void MethodThatGetsCalledOften()
  {
    // Imagine some useful code here
  }
  void MethodThatGetsCalledRarely()
  {
    T * __attribute__((__may_alias__)) p
      = (reinterpret_cast<T *>(_optionalObject._buf));
    if (_isObjectConstructed == false)
      {
        // demand-construct the heavy object, since we actually need to use it now
        (void) new (p) T();
        _isObjectConstructed = true;
      }
      p->DoSomething();
  }
  [etc.]

我认为让你的包含类只包含一个足够大的char数组来包含你的成员"object",然后使用placement new在char数组顶部初始化。它具有规范兼容以及交叉编译器的优点。唯一的问题是,您必须知道成员对象的字符大小,这可能会给您带来麻烦。

你不能让成员成为指针并使用new和delete是有原因的吗?

如果将_isObjectConstructed替换为指向对象的指针:

class Lightweight
{
public:
   Lightweight() : object(NULL) {/* empty */}
   ~Lightweight()
   {
      // call object's destructor, only if we ever constructed it
      if (object) object->~T();
   }
   void MethodThatGetsCalledOften()
   {
      // Imagine some useful code here
   }
   void MethodThatGetsCalledRarely()
   {
      if (!object)
      {
         // demand-construct the heavy object, since we actually need to use it now
         object = new (_optionalObject._buf) T();
      }
      object->DoSomething();
   }
private:
   union {
      char _buf[sizeof(T)];
      unsigned long long _thisIsOnlyHereToForceEightByteAlignment;
   } _optionalObject;
   T *object;
};

注意,没有GCC扩展,只有纯C++代码。

使用T*而不是bool甚至不会使Lightweight变得更大!

相关文章: