【问题标题】:C/C++ allocation of arrays of array like objects类似对象的数组的 C/C++ 分配
【发布时间】:2015-12-08 15:12:43
【问题描述】:

我主要是一名 C 程序员,我正在寻找一种快速而优雅的解决方案来完成我想要在 C++ 中做的事情。让我们考虑一下这个简单的数据结构

struct mystruct
{
    int * array1;
    int * array2;
    size_t size;
};

两个指针array1array2 被认为是两个长度为size 的数组。我需要大量相同的小尺寸(大约2**301.000.000.000)(大约100)。所有这些都将在同一时间被释放。我可以在 C 中执行以下操作,只需 one 调用 malloc 其中K 是我需要的结构数,N 是数组的大小

编辑版本(见下面的旧版本)

size_t NN = N * sizeof(int);
struct mystruct * my_objects = malloc(K * sizeof(struct mystruct));
int * memory = malloc(2*K*NN);
for(i=0; i<K; ++i)
{
    my_objects[i].size = N;
    my_objects[i].array1 = memory + 2*i*NN;
    my_objects[i].array2 = memory + (2*i+1)*NN;
}
...
free(my_objects);
free(memory);

这个版本不支持非常大的K 并且不允许我调整数组的大小。但是为此目的设计一些东西并不难。有没有办法在 C++ 中创建一个类,它是一种std::vector&lt;mystruct&gt;,禁止收缩,array1array2 的分配不会基于每个条目的动态分配?我确实想尽量减少内存分配的影响,因为K 非常很大。

旧版本:

size_t KK = K * sizeof(mystruct);
size_t NN = N * sizeof(int);
struct mystruct * my_objects = (struct mystruct *) malloc(KK + 2*K*NN);
for(i=0; i<K; ++i)
{
    my_objects[i].size = N;
    my_objects[i].array1 = (int *) (my_objects + KK + 2*i*NN);
    my_objects[i].array2 = (int *) (my_objects + KK + (2*i+1)*NN);
}

【问题讨论】:

  • 既然你提到std::vector你显然已经意识到了,那么困扰你的问题是什么?
  • std::vector 有调整大小功能
  • 我不知道如何使用std::vector&lt;mystruct&gt;array1array2 分配必要的内存...(我确实想尽量减少内存分配调用)。
  • 您可以通过两次分配轻松轻松地做到这一点。一个用于结构,另一个用于整数。我明白为什么你不希望每个对象分配一个,但是两个分配而不是一个不会有什么不同。
  • 我不是这方面的专家,但考虑到指针别名的限制,我想知道您提出的 C 代码是否真的合法。

标签: c++ c arrays struct


【解决方案1】:

这是我从 C 到 C++ 的字面翻译,它保持相同的内存布局:

std::unique_ptr<int[]> const memory(new int[2 * K * N]);

std::vector<mystruct> my_objects;
my_objects.reserve(K);

for (int i = 0; i < K; ++i)
{
    mystruct const tmp = {N, memory + 2*i*NN, memory + (2*i+1)*NN};
    my_objects.push_back(tmp);
}

【讨论】:

  • auto memory = std::make_unique&lt;int[]&gt;(2 * K * N); 这些天 IMO 更流行了。
  • @MinorThreat 这将初始化所有数组元素,而 new int[2 * K * N] 不会。在这种情况下,我试图模仿原始代码。
  • 理想情况下,这将被打包到一个 MyObjects 类中,该类在构造函数中进行展示的设置(以 KN 作为参数)并管理 unique_ptr&lt;&gt; 的生命周期.干得好。
  • 考虑在循环体中使用 emplace_back,而不是创建一个本地并使用 push_back:my_objects.emplace_back({N, memory + 2*i*NN, memory + (2*i+1)*NN});。显然复制省略不能使用 push_back:stackoverflow.com/questions/11875939/….
  • @FlorianKaufmann 他的结构没有合适的构造函数,所以emplace_back不能用这种方式初始化。
【解决方案2】:

下面进行两次内存分配,每个向量分配一次。当然,您必须确保ints 向量的寿命比mystructs 向量长,因为mystructs 的成员指的是ints 的成员。

  struct mystruct
  {
    int* array1;
    int* array2;
    std::size_t size;
  };

  std::vector<int> ints(N*2*K);
  std::vector<mystruct> mystructs(K);
  for (std::size_t i=0; i<K; i++) {
    mystruct& ms = mystructs[i];
    ms.array1 = &ints[2*N*i];
    ms.array2 = &ints[2*N*i+1];
    ms.size = N;
  }

更新: 正如 tp1 指出的那样,std::vector 可能会重置其内部数组,从而使所有指向它的指针无效。如果您从不添加或删除元素,那不是问题。如果这样做,请考虑使用std::deque 代替ints。但是,您在构造时也有更多的内存分配,请参阅What really is a deque in STL?。请注意,遗憾的是 C++ 不允许非常量元素的 const std::vector,请参阅 Const vector of non-const objects

【讨论】:

  • 从语义上讲,这个解决方案可能是最接近 OP 试图做的事情。但是:当心 RAII 和堆栈分配对象的奇思妙想!如果这个方法退出,这里使用的内存就会失效,你会访问到无效的内存,这也是我极力反对这种方案的原因。
  • 是的。 OP的问题超出了他打算使用代码片段的上下文。我假设 OP 作为 C 程序员知道悬空指针和内存管理的主题。我和你有同样的想法,这就是为什么我添加了注意myvectorints更长寿。
  • 获取向量内容的地址有点危险,因为向量会自动将向量的内容移动到内存中的另一个位置,最终会得到悬空指针。通常这样的指针应该是短暂的,因此指针只使用很短的时间。
  • @tp1 那也是。这实际上可能是更大的问题,尽管基于 OP 的用例,它可能并不重要。
【解决方案3】:

注意:解决方案创建时考虑到了最少的手动内存处理,在 OP 编辑​​之前,他的主要要求是 性能,因为 K 非常大。由于std::vector 仍在幕后进行内存分配,这不是一个快速 解决方案,只是一个优雅 解决方案。

可能会使用自定义内存分配器进行改进,但我认为@Simple 的答案是全面的,特别是如果封装在包装类中。



struct MyStruct
{
    std::vector< int > array1;
    std::vector< int > array2;
    std::size_t size;

    MyStruct( std::size_t init_size ) :
        array1( std::vector< int >( init_size ) ),
        array2( std::vector< int >( init_size ) ),
        size( init_size )
    {}
};

// ...

std::vector< MyStruct > my_objects( K, N );

根本没有动态内存分配。 (好吧,反正不是你。)

【讨论】:

  • 向量在后台执行动态内存分配。这是一个很好的解决方案(比我的稍微优雅一点),但是说“没有动态内存”是不准确的。
  • @Xirema:忍者。 ;-)
  • 好吧,一些 K=16777216 和 N=10 的基准测试:你的版本是 5.88 秒,我的是 0.237 秒。据我了解,向量正在进行动态分配,并且为 MyStruct 向量的 each 元素调用构造函数。
  • @V.Delecroix:嗯,这是关于性能的第一个词,并且要求以这种方式分配 160 MByte 的内存。
  • @V.Delecroix:你可以让 lead 了解这些信息。需要处理 100GB 的 int 数组并不是您的日常用例。 (而且我想知道您的设计是否会从一些改进中受益,我从来没有需要处理这么多,而且我一直在研究一些真正的数据吊索。)
【解决方案4】:

您在 C 中所做的是将一个数组从外部分配给您的结构,而不是将指针指向该数组的不同部分。

你可以用std::vector&lt;&gt; 做同样的事情——在你的结构之外定义一个巨大的向量,并将指针指向这个向量的不同部分。完全一样。

【讨论】:

    【解决方案5】:

    如果NK 在编译时是已知的,但在不同的地方可能不同,那么模板就可以工作:

    template <int N, int K>
    struct Memory {
      Memory() {
        for (int i=0; i < K; i++) {
          mystruct[i].array1 = data1[i];
          mystruct[i].array2 = data2[i];
          size[i] = N;
        }
      }
    
      struct mystruct {
        int * array1;
        int * array2;
        size_t size;
      } mystructs[K];
    
      int data1[K][N];
      int data2[K][N];
    };
    
    void f() {
      // The constructor sets up all the pointers.
      Memory *m<100,200> = new Memory<100,200>();
    
      .....
    }
    

    (我没有检查是否编译。)

    如果不知道这些值,那么我不会尝试在一次分配中执行此操作;进行两次分配更有意义,一次分配给mystruct 的数组,一次分配给整数。额外的开销很小,而且代码更易于维护。

    struct Memory {
      Memory(int N, int K) {
        mystructs = new mystruct[K];
        data = new int[2*K*N];
    
        for (int i=0; i < K; i++) {
          array1[i] = &data1[2*i*N];
          array2[i] = &data2[(2*i+1)*N];
          size[i] = N;
        }
      }
    
      struct mystruct {
        int * array1;
        int * array2;
        size_t size;
      } mystruct *mystructs;
    
      int *data;
    };
    

    (同样,我没有检查编译。)

    请注意,您的代码有 2*i*N*sizeof(int) 的地方有一个错误,因为 C 指针算法不计算字节数;它计算指针类型的倍数。在我的代码中,我通过获取数组项的地址来明确这一点,但数学是相同的。

    【讨论】:

      【解决方案6】:

      您想要做的事情可以使用 C++ 中完全相同的代码来完成。

      但是,在 c++ 中这是完全不可取的。 c++ 具有面向对象语义的原因是为了避免您正在考虑的情况。这是我将如何处理这个问题: 罢工>

      struct mystruct {
          vector<int> array1;
          vector<int> array2;
          mystruct(size_t size);
      }
      
      mystruct::mystruct(size_t size) {
          array1.resize(size);
          array2.resize(size);
      }
      
      int main() {
          vector<mystruct> mystructarray(numOfStructs, numOfElementsOfArray1AndArray2);
          //EDIT: You don't need to expressly call the mystruct constructor, it'll be implicitly called with the variable passed into the vector constructor.
          //Do whatever
          return 0;
      }
      

      vector 对象可以在运行时查询其大小,因此无需将大小存储为mystruct 的字段。而且由于您可以为结构定义构造函数,因此最好以这种方式处理对象的创建。最后,使用有效的构造函数,您可以使用向量初始化 mystruct 数组,并为 mystruct 的构造函数传入有效参数来构建向量。

      双重编辑组合:好的,让我们尝试不同的方法。

      根据您在 cmets 中的说明,听起来您需要分配大量内存。我认为这些数据在您的应用程序中具有特定含义,这意味着对您的数据使用通用数据结构没有多大意义。所以这就是我的提议:

      class mydata {
      private:
          size_t num_of_sets;
          size_t size_of_arrays;
      
          std::vector<int> data;
      
      public:
          mydata(size_t _sets, size_t _arrays)
              : data(_sets * _arrays * 2),
              num_of_sets(_sets),
              size_of_arrays(_arrays) {}
      
          int * const array1(size_t);
          int * const array2(size_t);
      };
      
      int * const mydata::array1(size_t index)
      {
          return &(data[index*size_of_arrays * 2]);
      }
      
      int * const mydata::array2(size_t index)
      {
          return &(data[index*size_of_arrays * 2 + size_of_arrays]);
      }
      
      int main(int argc, char** argv) {
          mydata data(16'777'216, 10);
      
          data.array1(5)[5] = 7;
          data.array2(7)[2] = 8;
      
          std::cout << "Value of index 5's array1 at index 5: " << data.array1(5)[5] << std::endl;
          std::cout << "Value of index 7's array2 at index 2: " << data.array2(7)[2] << std::endl;
          //Do Something
          return 0;
      }
      

      【讨论】:

      • 在这个版本中,不是为向量 mystructarray 的每个条目调用 mystruct 构造函数吗?
      • 是的。你应该这样做。对于未来的程序员来说,您进行内存分配的方式很难维护,并且只会对内存使用/性能方面的某些优化产生边际收益。
      • 您建议的速度要慢 10 倍以上,这对我的应用程序来说是不可接受的。这正是我所要求的:一个快速而优雅的解决方案。
      • @V.Delecroix:快速不等于优雅。至少,并非总是如此。
      • 好吧,正如我已经说过的:您可以在 C++ 中使用与在 C 中使用的完全相同的代码。没有什么能阻止您。但是您会遇到代码比应有的更难以维护的问题,这是任何编程语言中错误的第一大原因。
      猜你喜欢
      • 2018-11-28
      • 2017-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多