在c++中访问超额分配的内存
Accessing overallocated memory in C++
我有一个巨大的树,可以占用几个gb。节点结构如下:您会注意到,我将最后一个成员设置为大小为1的数组。这样做的原因是我可以过度分配具有灵活大小的Node
。类似于C原生支持的灵活数组成员。我可以使用std::unique_ptr<T[]>
或std::vector<T>
来代替,但问题是,这样会有双重动态分配,双重间接分配,以及每个树节点的额外缓存丢失。在我的最后一次测试中,这使我的程序多花了大约50%的时间,这对我的应用程序来说简直是不可接受的。
template<typename T>
class Node
{
public:
Node<T> *parent;
Node<T> *child;
/* ... */
T &operator[](int);
private;
int size;
T array[1];
};
实现operator[]
的最简单的方法是:
template<typename T>
T &Node::operator[](int n)
{
return array[n];
}
它应该在大多数正常的c++实现中正常工作。但由于c++标准允许疯狂的实现做数组边界检查,据我所知,这是在技术上调用未定义的行为。那我能做这个吗?
template<typename T>
T &Node::operator[](int n)
{
return (&array[0])[n];
}
我有点糊涂了。基本类型的[]
操作符只是*
的语法糖。因此,(&array[0])[n]
相当于(&*(array + 0))[n]
,我认为可以将其清理为array[n]
,使一切与第一个相同。好的,但是我仍然可以这样做。
template<typename T>
T &Node::operator[](int n)
{
return *(reinterpret_cast<T *>(reinterpret_cast<char *>(this) + offsetof(Node<T>, array)) + n);
}
我希望我现在从可能的未定义行为中解脱出来。也许内联汇编将显示我的意图更好。但我真的要这么做吗?有人能给我解释一下吗?
顺便说一下,T
总是一个POD类型。整个Node
也是POD
首先,实现可以自由地对所有类成员进行重新排序,但不重要的情况除外。您的案例并非微不足道,因为它具有访问说明符。除非你把你的类设置为POD,或者不管它在c++ 11中叫什么(琐碎的布局?),否则你不能保证你的数组实际上是最后布局的。
当然,c++中不存在灵活成员
然而,并不是所有的都失去了。分配一块足够大的内存来容纳你的类和你的数组,然后在开始放置你的类,并解释对象之后的部分(加上任何填充以确保正确对齐)作为数组。如果您有this
,则可以使用
reinterpret_cast<T*>(
reinterpret_cast<char*>(this) +
sizeof(*this) + padding))
,其中sizeof(T)
除以sizeof(*this) + padding
。
参考std::make_shared '。它还将两个对象打包到一个分配的内存块中。
越界数组访问的主要问题是那里没有对象。并不是越界索引本身导致了这个问题。现在,在你的情况下,大概在预定的位置有原始内存。这意味着您实际上可以通过赋值在那里创建一个POD对象。任何后续的读访问都将在那里找到该对象。
根本原因是C语言没有真正的数组边界。a[n]
就是*(a+n)
, 根据定义。因此,最初提出的两种形式已经相同了。
我稍微担心T array[1]
后面的任何填充,您将作为array[1]
的一部分访问。
您还想知道是否有其他方法。鉴于您最近关于"没有重新分配"的评论,我会将数组数据存储为指向堆分配存储的指针,但是:
树具有可预测的访问模式,从根到子。因此,我有一个Node::operator new
,并确保子节点直接分配在它们的父节点之后。这为您在遍历树时提供了参考的局部性。其次,我将为数组数据使用另一个分配器,并使它为父数组及其第一个子数组返回连续内存(当然,后面跟着它的第一个孙子)。
结果是节点和它的数组之间没有引用的局部性,但是你得到了树图和相关数组数据的引用局部性。
数组数据分配器很可能是树的一个普通池分配器。每次只分配256 KB的块,并一次将它们分成几个整数。您需要跟踪的整个状态是您已经分配了多少256 kB。这比std::vector<T, std::allocator>
可以实现的要快得多,因为它无法知道内存的生存时间与树的生存时间一样长。
- 在c++中为我自己的基于指针的数组分配内存的正确方法
- 给定一个指向堆分配内存的指针,智能指针实现如何为其找到合适的释放函数?
- 如果 const 不分配内存,为什么我可以获取 const 的地址?
- 在函数中分配内存时出现问题
- 如何为 std::vector 分配内存,然后稍后为某些元素调用构造函数?
- constexpr new 如何分配内存?
- 在构造函数中分配内存失败是如何冒泡的
- LLVM 传递以在特定地址分配内存
- CudaMalloc 在分配内存时失败
- 为什么它在不分配内存的情况下工作正常
- 为什么在正确解除分配内存时出现内存泄漏?
- 如何通过 malloc 为队列数组分配内存?
- vector是否为std::移动的对象连续分配内存
- 删除类成员的动态分配内存的最佳方法是什么
- 唯一指针是否在堆或堆栈上分配内存?
- 如果不分配内存,我如何能够为变量创建和分配值?
- std::initializer_list 堆是否分配内存?
- 如何按顺序或在指定的地址分配内存?
- 是否可以使用 malloc 为类对象分配内存?
- 迭代器是否分配内存(如指针)?