Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a c++ counterpart to posix_memalign?

Tags:

c++

When I call posix_memalign to allocate aligned memory for an object of type Foo in my C++ code, I am required to do a reinterpret_cast of the address of that pointer to void**.

In general when I encounter this situation, it implies that I am missing some language feature. That is, it feels like I am calling malloc in c++ when I should be calling new. , Is there a type-aware new equivalent for aligned memory allocation in c++?

like image 272
merlin2011 Avatar asked Dec 17 '14 01:12

merlin2011


2 Answers

I will start with the core advice first.

Foo* aligned_foo() {
  void* raw = 0;
  if(posix_memalign(&raw, 8, sizeof(Foo)))
    return 0; // we could throw or somehow communicate the failure instead
  try{
    return new(raw) Foo();
  }catch(...){
    free(raw);
    throw;
  }
}

then when you are done with the Foo* foo, do a foo->~Foo(); free(foo); instead of delete.

Note the lack of reinterpret_casts.


Here is an attempt to make it generic:

// note: stateless.  Deleting a derived with a base without virtual ~base a bad idea:
template<class T>
struct free_then_delete {
  void operator()(T*t)const{
    if(!t)return;
    t->~T();
    free(t);
  };
};
template<class T>
using aligned_ptr=std::unique_ptr<T,free_then_delete<T>>;

// the raw version.  Dangerous, because the `T*` requires special deletion:
template<class T,class...Args>
T* make_aligned_raw_ptr(size_t alignment, Args&&...args) {
  void* raw = 0;
  if(int err = posix_memalign(&raw, alignment, sizeof(T)))
  {
    if (err==ENOMEM)
      throw std::bad_alloc{};
    return 0; // other possibility is bad alignment: not an exception, just an error
  }
  try {
    // returns a T*
    return new(raw) T(std::forward<Args>(args)...);
  } catch(...) { // the constructor threw, so clean up the memory:
    free(raw);
    throw;
  }
}
template<class T,class...Args> // ,class... Args optional
aligned_ptr<T> make_aligned_ptr(size_t alignment=8, Args&&...args){
  T* t = make_aligned_raw_ptr<T>(alignment, std::forward<Args>(args)...);
  if (t)
    return aligned_ptr<T>(t);
  else
    return nullptr;
}

The unique_ptr alias aligned_ptr bundles the destroyer along with the pointer -- as this data requires destruction and free, not delete, this makes it clear. You can still .release() the pointer out, but you still have to do the steps.

like image 73
Yakk - Adam Nevraumont Avatar answered Nov 17 '22 05:11

Yakk - Adam Nevraumont


Actually, you don't want to do a reinterpret_cast because then your Foo constructor isn't called. If you need to allocate memory from a special place, you then call placement new to construct the object in that memory:

void* alloc;
posix_memalign(&alloc, 8, sizeof(Foo));
Foo* foo = new (foo) Foo();

The only other way (pre C++11) would be overriding the new operator for your class. That works if you have a particular class that always requires this special allocation:

class Foo {
    void* operator new(size_t size) {
        void* newobj;
        posix_memalign(&newobj, 8, sizeof(Foo));
        return newobj;
    }
};

Then anytime you call new Foo() it will invoke this allocator. See http://en.cppreference.com/w/cpp/memory/new/operator_new for more information. Overriding operator new and operator delete can be done for individual classes or globally.

like image 3
Jay Miller Avatar answered Nov 17 '22 05:11

Jay Miller