简体   繁体   中英

C++ fixed size allocator: is this UB?

I'm writing a fixed size allocator. I'm curious what I'm doing is UB.

#include <type_traits>
#include <stdexcept>

template <typename T>
concept DiskAllocable = std::is_same_v<std::remove_cvref_t<T>, T>
&& std::is_trivially_copyable_v<T> && (sizeof(T) % alignof(T) == 0);

template <DiskAllocable T, size_t Count = 1> class AllocatorFixed {
  static_assert(Count > 0, "count should be nonzero");
  static constexpr size_t block_size_ = (sizeof(T) * Count);
  static_assert(block_size_ >= sizeof(T *),
                "block size should be at least pointer size");

  union Chunk {
    T *next_;
    T object_[Count];
  };

  T *pool_ = nullptr;
  size_t pool_size_ = 0;
  Chunk *free_ = nullptr;

public:
  using value_type = T;

  AllocatorFixed(unsigned char *pool_ptr, size_t pool_byte_size) {
    if (!pool_ptr) {
      throw std::invalid_argument("pool ptr is null");
    }
    if (pool_byte_size < block_size_) {
      throw std::invalid_argument("pool byte size is too small");
    }
    pool_ = reinterpret_cast<T *>(pool_ptr);
    pool_size_ = pool_byte_size / block_size_;

    T* chunk_ptr = pool_;
    for (size_t i = 0; i < pool_size_; i++, chunk_ptr += Count) {
      auto chunk = reinterpret_cast<Chunk *>(chunk_ptr);
      chunk->next_ = chunk_ptr + Count;
    }
    free_ = reinterpret_cast<Chunk *>(pool_);
  }

 // ... other details ...

};

I'm using Chunk as a union of actual chunk block and T* , and I'm writing all Chunk.next_ as the pointer value to the next Chunk . I'm worried if this is UB.

Just to close the loop on the long discussion in the comments, I strongly suggest that you make the pool ownership part of the AllocatorFixed class itself. This will allow you to avoid all of the problems you're asking about with aliasing, and greatly simplify the code in the process. For example:

template <DiskAllocable T, size_t Count = 1, size_t NumChunks = 1> class AllocatorFixed {
  union Chunk {
    Chunk *next_;
    std::aligned_storage<sizeof(T), alignof(T)> object_[Count];
  } chunks_[NumChunks];

  Chunk *free_ = nullptr;

public:
  using value_type = T;

  AllocatorFixed() {
    for (size_t i = 0; i < NumChunks - 1; i++) {
      chunks_[i].next_ = &chunks_[i+1];
    }
    chunks_[NumChunks-1].next_ = nullptr;
    free_ = &chunks_[0];
  }

 // ... other details ...

};

Everything here is perfectly defined and legal, construction is trivial, and the rest of your implementation should be extremely straightforward.

You could even adapt this to mmapped files as requested using placement new. For example:


using MyAllocator = AllocatorFixed<int, 2, 20>;

...

    MyAllocator *pAllocator = nullptr;
    char *ptr = (char *)mmap(...);
    pAllocator = ::new(ptr) MyAllocator();

...

Disclaimer: this is one suggestion to get around the specific problems you asked about. I doubt this will apply perfectly to your exact motivating situation, but hopefully it gives you a few additional tools to consider.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM