启用 MSVC 调试迭代器时发生堆栈分配器访问冲突
Stack allocator access violation when enabling MSVC debug iterator
我制作了一个简单的堆栈分配器,可用于在堆栈上分配小容器。
我已经使用这个 class 一段时间了,它一直运行良好。
然而,今天我重新打开调试迭代器 (_ITERATOR_DEBUG_LEVEL=2
),我突然在由此开关激活的调试代码中遇到访问冲突。
模板编程和标准库编码约定的混合使得调试变得非常困难,而且我也不是分配器方面的专家。我是否违反了分配器的某种规则?
下面的代码应该能够重现错误。
#include <memory>
#include <vector>
template <typename T, size_t N, template <typename> typename Allocator = std::allocator>
class StackAllocator : public Allocator<T>
{
public:
using base = Allocator<T>;
using pointer_type = typename base::pointer;
using size_type = typename base::size_type;
StackAllocator() noexcept = default;
StackAllocator(const StackAllocator& a_StackAllocator) noexcept = default;
template <typename U>
StackAllocator(const StackAllocator<U, N, Allocator>& a_StackAllocator) noexcept
{
}
pointer_type allocate(size_type a_Size, void* a_Hint = nullptr)
{
if (!m_Active && a_Size <= N)
{
m_Active = true;
return GetStackPointer();
}
else
{
return base::allocate(a_Size, a_Hint);
}
}
void deallocate(pointer_type a_Pointer, size_type a_Size)
{
if (a_Pointer == GetStackPointer())
{
m_Active = false;
}
else
{
base::deallocate(a_Pointer, a_Size);
}
}
template <class U>
struct rebind
{
using other = StackAllocator<U, N, Allocator>;
};
private:
pointer_type GetStackPointer()
{
return reinterpret_cast<pointer_type>(m_Data);
}
std::aligned_storage_t<sizeof(T), alignof(T)> m_Data[N];
bool m_Active = false;
};
template <typename T, size_t N>
class StackVector : public std::vector<T, StackAllocator<T, N>>
{
public:
using allocator_type = StackAllocator<T, N>;
using base = std::vector<T, allocator_type>;
StackVector() noexcept(noexcept(allocator_type())):
StackVector(allocator_type())
{
}
explicit StackVector(const allocator_type& a_Allocator) noexcept(noexcept(base(a_Allocator))):
base(a_Allocator)
{
base::reserve(N);
}
using base::vector;
};
int main(int argc, char* argv[])
{
StackVector<size_t, 1> v;
return v.capacity();
}
我正在使用 MSVC 2017 (15.4.0)。
问题确实是违反了@Igor 所说的要求。我通过将堆栈数据存储在分配器之外解决了这个问题。现在 StackVector 是数据的所有者,分配器及其副本都指向该内存,确保一个分配器分配的内存可以由该分配器的副本释放。
这个解决方案确实给用户带来了一些复杂性(现在必须管理 2 个对象而不是 1 个),但是在满足分配器要求的同时似乎没有其他方法,并且可以通过包装来避免容器也是。
可以在 Chromium's StackAllocator 中找到此解决方案的一个很好的例子。
我制作了一个简单的堆栈分配器,可用于在堆栈上分配小容器。
我已经使用这个 class 一段时间了,它一直运行良好。
然而,今天我重新打开调试迭代器 (_ITERATOR_DEBUG_LEVEL=2
),我突然在由此开关激活的调试代码中遇到访问冲突。
模板编程和标准库编码约定的混合使得调试变得非常困难,而且我也不是分配器方面的专家。我是否违反了分配器的某种规则?
下面的代码应该能够重现错误。
#include <memory>
#include <vector>
template <typename T, size_t N, template <typename> typename Allocator = std::allocator>
class StackAllocator : public Allocator<T>
{
public:
using base = Allocator<T>;
using pointer_type = typename base::pointer;
using size_type = typename base::size_type;
StackAllocator() noexcept = default;
StackAllocator(const StackAllocator& a_StackAllocator) noexcept = default;
template <typename U>
StackAllocator(const StackAllocator<U, N, Allocator>& a_StackAllocator) noexcept
{
}
pointer_type allocate(size_type a_Size, void* a_Hint = nullptr)
{
if (!m_Active && a_Size <= N)
{
m_Active = true;
return GetStackPointer();
}
else
{
return base::allocate(a_Size, a_Hint);
}
}
void deallocate(pointer_type a_Pointer, size_type a_Size)
{
if (a_Pointer == GetStackPointer())
{
m_Active = false;
}
else
{
base::deallocate(a_Pointer, a_Size);
}
}
template <class U>
struct rebind
{
using other = StackAllocator<U, N, Allocator>;
};
private:
pointer_type GetStackPointer()
{
return reinterpret_cast<pointer_type>(m_Data);
}
std::aligned_storage_t<sizeof(T), alignof(T)> m_Data[N];
bool m_Active = false;
};
template <typename T, size_t N>
class StackVector : public std::vector<T, StackAllocator<T, N>>
{
public:
using allocator_type = StackAllocator<T, N>;
using base = std::vector<T, allocator_type>;
StackVector() noexcept(noexcept(allocator_type())):
StackVector(allocator_type())
{
}
explicit StackVector(const allocator_type& a_Allocator) noexcept(noexcept(base(a_Allocator))):
base(a_Allocator)
{
base::reserve(N);
}
using base::vector;
};
int main(int argc, char* argv[])
{
StackVector<size_t, 1> v;
return v.capacity();
}
我正在使用 MSVC 2017 (15.4.0)。
问题确实是违反了@Igor 所说的要求。我通过将堆栈数据存储在分配器之外解决了这个问题。现在 StackVector 是数据的所有者,分配器及其副本都指向该内存,确保一个分配器分配的内存可以由该分配器的副本释放。
这个解决方案确实给用户带来了一些复杂性(现在必须管理 2 个对象而不是 1 个),但是在满足分配器要求的同时似乎没有其他方法,并且可以通过包装来避免容器也是。
可以在 Chromium's StackAllocator 中找到此解决方案的一个很好的例子。