类似对象的数组的 C/C++ 分配

Posted

技术标签:

【中文标题】类似对象的数组的 C/C++ 分配【英文标题】:C/C++ allocation of arrays of array like objects 【发布时间】: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 代码是否真的合法。 【参考方案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:***.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 中使用的完全相同的代码。没有什么能阻止您。但是您会遇到代码比应有的更难以维护的问题,这是任何编程语言中错误的第一大原因。

以上是关于类似对象的数组的 C/C++ 分配的主要内容,如果未能解决你的问题,请参考以下文章

ucosiii浅析内核对象-软件定时器

C++C++自学旅程:对象数组和对象指针

使用数组对象计算 Spark RDD 中的不同文本

C 代码中的 Java 数组

java数组小结

JS遍历一个数组里包含数组对象然后组成三个新的数组,怎么遍历?