标准分配器是否需要分配连续内存?

时间:2013-07-26 09:41:10

标签: c++ memory-management c++11

我无法在C ++ 11标准中找到是否需要符合标准的分配器来返回指向连续内存块的指针。

std::vector(23.3.6.1/1)上的连续存储要求似乎意味着这样(否则似乎不可能将std::vector与任意符合标准的分配器一起使用)。但任何澄清都是最受欢迎的。

一个等价的问题是:我是否可以通过指针算术来移动allocate()返回的内存块(可能是在将pointer返回的泛型allocate()类型转换为普通原始C ++之后如例如here)所述的指针?

3 个答案:

答案 0 :(得分:5)

是的,它必须是连续的,因为allocator::pointer上的指针算法按预期工作。

如果你考虑一下,返回的内存很少是物理连续的。它看起来只是连续的,因为现代CPU具有虚拟内存,X*在此虚拟内存中被解释。

答案 1 :(得分:2)

鉴于分配器A,我会说A提供连续的内存,如果p返回的A::allocate(n) std::addressof(*p) + k == std::addressof(*(p + k)) k[0,n)位于std::addressof(*(p + n - 1)) + 1 == std::addressof(*p) + nvector区间时。{/ p>

我没有看到分配器要求中需要此属性(第17.6.3.5节[allocator.requirements]),但我无法想象如何实现vector::data()(尤其是vector ) 没有它。 (a)我在分配器要求中缺少某些内容,(b)分配器要求未得到规定,或者(c)#include <cstddef> #include <iostream> #include <iterator> #include <limits> #include <memory> template <typename T> class ScaledPointer : public std::iterator<std::random_access_iterator_tag, T> { T* ptr; public: ScaledPointer() = default; ScaledPointer(T* ptr) : ptr(ptr) {} template <typename U> explicit ScaledPointer(U* ptr) : ptr(static_cast<T*>(ptr)) {} template <typename U> explicit ScaledPointer(const ScaledPointer<U>& other) : ptr(static_cast<T*>(other.ptr)) {} explicit operator bool () const { return bool{ptr}; } T& operator * () const { return *ptr; } T* operator -> () const { return ptr; } T& operator [] (std::ptrdiff_t n) const { return ptr[2 * n]; } ScaledPointer& operator ++ () { ptr += 2; return *this; } ScaledPointer operator ++ (int) { ScaledPointer tmp(*this); ++*this; return tmp; } ScaledPointer& operator -- () { ptr -= 2; return *this; } ScaledPointer operator -- (int) { ScaledPointer tmp(*this); --*this; return tmp; } template <typename U, typename V> friend bool operator == (const ScaledPointer<U>& u, const ScaledPointer<V>& v) { return u.ptr == v.ptr; } template <typename U, typename V> friend bool operator != (const ScaledPointer<U>& u, const ScaledPointer<V>& v) { return !(u == v); } template <typename U, typename V> friend bool operator < (const ScaledPointer<U>& u, const ScaledPointer<V>& v) { return u.ptr < v.ptr; } template <typename U, typename V> friend bool operator > (const ScaledPointer<U>& u, const ScaledPointer<V>& v) { return v < u; } template <typename U, typename V> friend bool operator <= (const ScaledPointer<U>& u, const ScaledPointer<V>& v) { return !(v < u); } template <typename U, typename V> friend bool operator >= (const ScaledPointer<U>& u, const ScaledPointer<V>& v) { return !(u < v); } ScaledPointer& operator += (std::ptrdiff_t n) { ptr += 2 * n; return *this; } friend ScaledPointer operator + (const ScaledPointer& u, std::ptrdiff_t n) { ScaledPointer tmp = u; tmp += n; return tmp; } ScaledPointer& operator -= (std::ptrdiff_t n) { ptr -= 2 * n; return *this; } friend ScaledPointer operator - (const ScaledPointer& u, std::ptrdiff_t n) { ScaledPointer tmp = u; tmp -= n; return tmp; } friend std::ptrdiff_t operator - (const ScaledPointer& a, const ScaledPointer& b) { return (a.ptr - b.ptr) / 2; } }; template <typename T> class ScaledAllocator { public: typedef ScaledPointer<T> pointer; typedef T value_type; typedef std::size_t size_type; pointer allocate(size_type n) { const std::size_t size = (n * (2 * sizeof(T))); void* p = ::operator new(size); std::cout << __FUNCTION__ << '(' << n << ") = " << p << std::endl; std::fill_n((unsigned*)p, size / sizeof(unsigned), 0xFEEDFACEU); return pointer{p}; } void deallocate(pointer p, size_type n) { std::cout << __FUNCTION__ << '(' << &*p << ", " << n << ')' << std::endl; ::operator delete(&*p); } static size_type max_size() { return std::numeric_limits<size_type>::max() / 2; } template <typename U, typename V> friend bool operator == (const ScaledAllocator<U>&, const ScaledAllocator<V>&) { return true; } template <typename U, typename V> friend bool operator != (const ScaledAllocator<U>&, const ScaledAllocator<U>&) { return false; } }; #include <algorithm> #include <vector> int main() { using namespace std; cout << hex << showbase; vector<unsigned, ScaledAllocator<unsigned>> vec = {0,1,2,3,4}; for_each(begin(vec), end(vec), [](unsigned i){ cout << i << ' '; }); cout << endl; auto p = vec.data(); for(auto i = decltype(vec.size()){0}, n = vec.size(); i < n; ++i) cout << p[i] << ' '; cout << endl; } 对其分配器施加了超出一般要求的额外要求。

以下是不提供连续内存(paste of this code)的分配器的“简单”示例:

n

当要求为ScaledAllocator项目分配空间时,2 * n会为ScaledAllocator分配空间。它的指针类型也为其指针算法执行必要的缩放。实际上,它分配了一个包含2n个项目的数组,并且只使用偶数编号的插槽来存储数据。

任何人都可以看到allocate(n)无法满足的分配器要求吗?

编辑:这个问题的答案批判性地取决于标准对成员函数n在分配器要求表中的影响的描述的含义:“为T类型的对象分配了内存p == allocate(n)但是没有构造对象。“我想我们都同意,这意味着给定p + k然后k[0,n]中所有p + k的有效指针,而k对{[0,n)是无效的在std::vector::data()中的1}}。换句话说,在分配器的指针类型的域中连续的内存块。

什么不清楚 - 虽然它是ContiguousAllocator的描述非常间接暗示 - 是内存还需要在原始指针的域中连续(我的第一段中详述的正式命题) 。如果标准要么(a)明确适用于所有分配器的连续性要求,或者(b)将该要求添加到std::vector概念并指定ContiguousAllocator需要{{{1}},那将是很好的。 1}}。

答案 2 :(得分:1)

这取决于contiguous的含义。你的程序看到的内存肯定是连续的,或者它不会“正常”计算偏移/索引到数组,等等。如果你分配10个整数值,你希望ptr[0]是第一个,而ptr[9]是最后一个 - 因为ptr只是一个指针,它只能指向一个单个连续的内存块。

在引擎盖下,在真实的物理内存中,它可能是连续的或不连续的 - 这是操作系统确定和决定的东西,它可能从“任何地方”给应用程序内存。

相关问题