Мои знания стека по сравнению с кучей очень просты, но когда дело доходит до массивов, из того, что я знаю, в стеке создается нечто подобное
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
элементы, где каждый элемент является float[n]
, который в моем случае 100 * 4 = 400
байт. Теперь все это имеет смысл. Спасибо!
Вы написали свои экстенты массива задом наперед. Это работает:
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
элементы за один раз, в соответствии с типом массива.
Array<float[n]>* array = new Array<float[n]>(m);
Что здесь происходит два выделение кучи. Array
объект будет размещен в куче, потому что вы использовали new
создать его. новое выражение вызывает 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]
не идет в конце.
Вся память уходит в кучу. Компилятор выделяет один гигантский кусок памяти для массива массивов и устанавливает индексирование так, чтобы он был доступен.
И как в стороне, если кто-либо когда-либо копирует или назначает ваш Array
класс вы утечки памяти и / или двойного удаления.
В соответствии
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
указывает на выделение кучи. Хотя по умолчанию это так, когда используется в размещение режиме, фактическое распределение вполне может быть в стеке.
(1) Обратите внимание, что C ++ принимает его как таковой, потому что n
объявляется как константа, и, таким образом, результирующий тип может быть оценен во время компиляции. Удалить const
черта определения n
и компилятор будет жаловаться.