在c++中访问超额分配的内存

Accessing overallocated memory in C++

本文关键字:分配 内存 c++ 访问      更新时间:2023-10-16

我有一个巨大的树,可以占用几个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>可以实现的要快得多,因为它无法知道内存的生存时间与树的生存时间一样长。