我对堆栈的了解与堆相比是非常基本的,但是当涉及到数组时,从我所知道的内容就是在堆栈上创建的
float x[100];
而类似的东西是在堆上创建的
float* x = new float[100];
但是如果我创建一个模板数组类并以“堆栈”数组类型(如float[100]
)传递它会发生什么?例如:
#include <iostream>
using namespace std;
template <class T>
class Array {
public:
int size;
T* data;
Array(int size_) : size(size_) {
data = new T[size];
}
~Array() {
delete [] data;
}
};
int main() {
int m = 1000000;
const int n = 100;
Array<float[n]>* array = new Array<float[n]>(m);
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
array->data[i][j] = i * j;
cout << array->data[10][9] << endl;
delete array;
}
这到底发生了什么?这个内存是在堆栈上还是在堆上创建的?我的猜测是堆,但这是如何工作的?编译器是否分配了一个大的内存块,然后存储每个n
个元素索引到它的指针?或者它是否分配了许多较小的内存块(不一定是连续的),并存储指向每个块的指针?
此外,如果没有模板的帮助,我似乎无法做到这一点。具体来说,此代码无法编译:
int m = 1000;
const int n = 100;
(float[n])* array = new (float[n])[m];
这里发生了什么?
修改
感谢大家的语法提示。我真正感兴趣的是块中发生的事情
int m = 1000;
const int n = 100;
float (*array)[n] = new float[m][n];
但我不知道如何在不使用模板的情况下编写它。我真正感兴趣的一件事是,如果编译器将其分配为堆上的一个大块,那么如何使用语法array[i][j]
来访问特定元素而不存储指向每个第n个元素的指针?然后我意识到,由于n
是常量,sizeof(float[n])
是固定的,所以当你创建数组时,编译器会分配一个m
元素数组,其中每个元素都是{{1}在我的例子中,float[n]
字节。现在一切都有道理。谢谢!
答案 0 :(得分:3)
Array<float[n]>* array = new Array<float[n]>(m);
这里发生的是两个堆分配。 Array
对象将在堆上分配,因为您使用new
来创建它。 new-expression 调用Array
构造函数,该构造函数再次使用new
来分配数组data
;因此data
也在堆上分配。
最好这样做:
Array<float[n]> array(m);
这会在堆栈上分配array
(因此它会在块的末尾自动销毁)。但是,虽然array
对象本身位于堆栈上,但数据仍然存储在堆上,因为它是在Array
构造函数中的堆上分配的。这与您拥有std::vector
或std::string
局部变量时的情况类似。
此外,如果没有模板的帮助,我似乎无法做到这一点。具体来说,此代码无法编译:
这只是因为你的语法错误。正确的语法是:
float (*array)[n] = new float[m][n];
左侧显示声明指向数组的指针的正确方法。对于右侧,您需要一组m
float[n]
s。这表示为float[m][n]
; [m]
最后没有结束。
答案 1 :(得分:2)
您已向后编写数组范围。这有效:
int m = 1000;
const int n = 100;
float (*array)[n] = new float[m][n];
delete[] array;
如果要以相同的顺序保留数组范围,可以使用类型别名或适当的模板:
using A = float[n];
A* array = new A[m];
或
// at file scope
template<typename T, unsigned N> using add_extent = T[N];
// ...
add_extent<float, n>* array = new add_extent<float, n>[m];
无论是在堆栈还是堆上,多维数组都被分配为m*n
元素的单个块。当您将指针索引到数组类型(如float (*array)[n]
)时,指针会按照数组类型的步幅一次递增n
个元素。
答案 2 :(得分:1)
所有内存都在堆上。编译器为数组数组分配一个巨大的内存块,并设置索引以便它可以访问。
另外,如果有人复制或分配你的Array
课程,你会泄漏内存和/或双重删除。
答案 3 :(得分:1)
在第
行Array<float[n]>* array = new Array<float[n]>(m);
在堆上分配Array<T>
的实例。你已经明白了,一般来说你的第一份关于分配的陈述。
也许令人困惑的部分是使用float[n]
作为模板参数?
模板参数T
,由class
定义中的关键字Array
表示,代表一种类型。它本身并不涉及任何形式的分配。
为了证明这一点,让我们写一个简单的模板,它不会使用它的参数:
#include <cassert>
using namespace std;
template <typename T>
class A {
};
int main(){
A<float[100]> a1;
A<float[1000]> a2;
float f[100];
assert(sizeof(a1) == sizeof(a2));
cout << "a1 : " << sizeof(a1) << endl;
cout.<< "f : " << sizeof(f) << endl;
}
输出:
a1 : 1
f : 400
所以float[n]
这里确实是一个类型(1)。
另一方面,当您使用关键字new
时,您知道堆上正在分配某些内容。正如我所说,array
变量将指向堆中的内存块。此外,模板本身包含堆分配(同样,关键字new
)。
最后,我想细化new
表示堆分配的基本前提。虽然默认情况下是这种情况,但在placement模式下使用时,实际分配很可能在堆栈上。
<子>
(1)请注意,C ++接受它是因为n
被声明为常量,因此可以在编译时评估结果类型。删除const
定义的n
特征,编译器会抱怨。
子>