6

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++?

merlin2011
  • 71,677
  • 44
  • 195
  • 329
  • I thought it would be aligned by default (when using new) unless you purposefully made sure it wasn't? – Borgleader Dec 17 '14 at 01:11
  • @Borgleader, I thought so too, but it is not. I checked at runtime with an `assert` on the last 6 bits of the address, which failed. – merlin2011 Dec 17 '14 at 01:11
  • 2
    Use the `alignas` keyword? – Kerrek SB Dec 17 '14 at 01:12
  • @KerrekSB, I actually hadn't heard of that. I am trying that now to see if it works with my ancient compiler `g++ 4.4.7` and also passes the assert test. – merlin2011 Dec 17 '14 at 01:15
  • @KerrekSB, Unfortunately `g++ 4.4.7` does not support that and my project is currently still tied to this compiler. – merlin2011 Dec 17 '14 at 01:17
  • 1
    Overload new for `Foo` to use `posix_memalign`? – Steve Vinoski Dec 17 '14 at 01:21
  • 1
    Your version of GCC might support alignment extensions that work similarly to the now standardized alignas. See https://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html. – Jay Miller Dec 17 '14 at 01:27
  • reinterpret_cast is overkill: `void* p; if (!posix_memalign(&p, al, sz)) return static_cast(p);` (this is just a simplified version of Yakk's answer, but worth repeating) – Jonathan Wakely Dec 17 '14 at 02:38

3 Answers3

5

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • @dyp on failure (non-zero return value) we abort and return a still-empty `retval` – Yakk - Adam Nevraumont Dec 17 '14 at 02:03
  • @dyp fixed. Added `alignment` argument. I could make the error codes throw, but ... I am lazy. I guess throwing out of memory makes sense. ... that would work: and the fact that `aligned_ptr` cannot throw in construction also makes one warm and fuzzy. I was attempting to enable NRVO, so all paths return `retval`. The OP indicated they are using some ancient compiler, hence the bevy of "well, you don't have to do this", and `return {};` is less pretty in C++03. – Yakk - Adam Nevraumont Dec 17 '14 at 02:07
  • Not sure where you'd `return {};`. For the first return `return nullptr;` would work, because that ctor is not explicit. The second return cannot use `{}`, because that ctor *is* explicit. – dyp Dec 17 '14 at 02:12
  • @dyp I fixed another bug, and inverted the advice -- put the concrete (short) version first, with your `return new(raw) Foo();` That one is clean enough I almost want to use it as the core of the `unique_ptr` one -- have the `unique_ptr` one just wrap it with a deleter. – Yakk - Adam Nevraumont Dec 17 '14 at 02:13
  • @dyp rewritten. I think it is cleaner with the `_raw_ptr` and `_ptr` versions. Now throws on out of memory, like `new`. – Yakk - Adam Nevraumont Dec 17 '14 at 02:22
  • Nice! Why is there a conditional conversion in `make_aligned_ptr`? Wouldn't just `return aligned_ptr(t)` do? Might it be hard to detect an alignment error if you just get a null pointer back? – dyp Dec 17 '14 at 02:34
  • Why do you call `free()` on `raw` when it was created with `new`? – David G Dec 20 '14 at 01:40
  • @0x499 it was allocated with `posix_memalign` and constructed with placement `new`. `posix_memalign` says to use `free`. I manually destruct to invert placement `new`. – Yakk - Adam Nevraumont Dec 20 '14 at 13:36
3

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.

Jay Miller
  • 2,184
  • 12
  • 11
  • I get a compiler error (or maybe promoted warning) if I don't do the `reinterpret_cast`. – merlin2011 Dec 17 '14 at 01:33
  • Also I don't mind at all that the constructor is not called (I already do a placement new later), but I care a great deal about the alignment. – merlin2011 Dec 17 '14 at 01:34
  • Sorry, I was thinking reinterpret cast in the other direction (to Foo). Indeed, Foo** needs the cast to void**. – Jay Miller Dec 17 '14 at 01:39
  • I think this `reinterpret_cast` violates the strict aliasing rule. You could try to avoid that by writing it like: `void* foo_v; posix_memalign(&foo_v, 8, sizeof(Foo)); auto foo = new (foo_v) Foo();` – dyp Dec 17 '14 at 01:48
  • If you ever say `std::vector`, you'll be in for a surprise :-S – Kerrek SB Dec 17 '14 at 09:12
  • @KerrekSB I assume the surprise you're referring to is that overriding `operator new` only affects objects allocated with `new` and not placement new (as in the case of `vector`) or stack allocated objects. That is a limitation for sure. I was trying to focus on what language features might be available. I actually like Yakk's answer better. – Jay Miller Dec 17 '14 at 13:49
1

C++11 has added native language support for alignment declarations and aligned allocation.

You can specify alignas(N) on your type in C++11 to specify the minimum alignment for new objects, which the default new will respect.

Example from cppreference:

struct alignas(16) sse_t { // SSE-safe struct aligned on 16-byte boundaries
    float v[4];
};

then you can simply do

sse_t *ssevec = new sse_t;

For a replacement for posix_memalign, you can use std::aligned_storage<sizeof(T), N>, also in C++11.

nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 1
    AFAIK, the alignas specifier only works for variable declarations (both static/global and local/stack variables). You can specify it either at the declaration site as a qualifier of the declaration's type, or on one of the classes we've used for a declaration. – Lluís Vilanova Jan 10 '17 at 01:03
  • 1
    This answer is partly wrong! In C++11, `new` does not respect in very case `alignas()`: https://stackoverflow.com/questions/15511909/does-the-alignas-specifier-work-with-new – Flow Apr 27 '17 at 14:56