类似对象的数组的 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;
;
两个指针array1
和array2
被认为是两个长度为size
的数组。我需要大量相同的小尺寸(大约2**30
或1.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<mystruct>
,禁止收缩,array1
和array2
的分配不会基于每个条目的动态分配?我确实想尽量减少内存分配的影响,因为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<mystruct>
为array1
和array2
分配必要的内存...(我确实想尽量减少内存分配调用)。
您可以通过两次分配轻松轻松地做到这一点。一个用于结构,另一个用于整数。我明白为什么你不希望每个对象分配一个,但是两个分配而不是一个不会有什么不同。
我不是这方面的专家,但考虑到指针别名的限制,我想知道您提出的 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<int[]>(2 * K * N);
这些天 IMO 更流行了。
@MinorThreat 这将初始化所有数组元素,而 new int[2 * K * N]
不会。在这种情况下,我试图模仿原始代码。
理想情况下,这将被打包到一个 MyObjects
类中,该类在构造函数中进行展示的设置(以 K
和 N
作为参数)并管理 unique_ptr<>
的生命周期.干得好。
考虑在循环体中使用 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 程序员知道悬空指针和内存管理的主题。我和你有同样的想法,这就是为什么我添加了注意myvector
比ints
更长寿。
获取向量内容的地址有点危险,因为向量会自动将向量的内容移动到内存中的另一个位置,最终会得到悬空指针。通常这样的指针应该是短暂的,因此指针只使用很短的时间。
@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<>
做同样的事情——在你的结构之外定义一个巨大的向量,并将指针指向这个向量的不同部分。完全一样。
【讨论】:
【参考方案5】:如果N
和K
在编译时是已知的,但在不同的地方可能不同,那么模板就可以工作:
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++ 分配的主要内容,如果未能解决你的问题,请参考以下文章