Подтвердить что ты не робот

Почему новый int [n] допустим, когда int array [n] не является?

Для следующего кода:

foo(int n){
    int array[n];
}

Я понимаю, что это недопустимый синтаксис и что он недопустим, потому что для стандарта С++ требуется, чтобы размер массива устанавливался во время компиляции (хотя некоторые компиляторы поддерживают следующий синтаксис).

Однако я также понимаю, что допустимый синтаксис следующий:

bar(int n){
    int *array = new int[n];
}

Я не понимаю, почему это разрешено, разве это не то же самое, что создавать массив, где размер определяется во время выполнения? Правильно ли это делать или я должен использовать вектор, если мне нужно это сделать?

4b9b3361

Ответ 1

Это потому, что первое выделено в стеке, а второе - в куче.

Когда вы выделяете что-то в стеке, знание размера объекта имеет важное значение для правильного его построения. C99 позволяет определять размер во время выполнения, и это создает некоторые сложности при создании и демонтаже вышеупомянутого стека, поскольку вы не можете рассчитать его размер во время компиляции. Машинный код должен быть испущен для выполнения указанного вычисления во время выполнения программы. Вероятно, это основная причина, почему эта функция не была включена в стандарт С++.

Напротив, куча не имеет фиксированной структуры, как следует из названия. Блоки любого размера могут быть выделены без особого порядка, если они не перекрываются, и у вас достаточно (виртуальной) памяти¹. В этом случае знание размера во время компиляции не имеет значения.

Также помните, что стек имеет ограниченный размер, в основном для обнаружения бесконечных рекурсий, прежде чем они будут потреблять всю доступную память. Обычно предел фиксируется около 1 МБ, и вы редко достигаете этого. Если вы не выделите большие объекты, которые должны быть помещены в кучу.

Как вам следует использовать, возможно, std::vector<int>. Но это действительно зависит от того, что вы пытаетесь сделать.

Также обратите внимание, что С++ 11 имеет класс std::array, размер которого должен быть известен во время компиляции. С++ 14 должен был ввести std::dynarray, но он был отложен, потому что еще есть много работы относительно распределения стека неизвестного размера компиляции.


Блоки

¹ обычно выделяются последовательно по соображениям производительности, но это не требуется.

², как указано, зная, что размер во время компиляции не является жестким требованием, но делает вещи проще.

Ответ 2

В первом случае вы произвольно выделяете пространство памяти для хранения целых чисел. Это делается, когда программа скомпилирована и поэтому объем хранилища негибкий.

В последнем случае вы динамически выделяете пространство памяти для хранения целых чисел. Это делается при запуске программы, поэтому объем требуемой памяти может быть гибким.

Второй вызов - это фактически функция, которая говорит с операционной системой, чтобы пойти и найти место в памяти для использования. Этот же процесс не выполняется в первом случае.

Ответ 3

int array[n] выделяет массив фиксированной длины в стеке вызовов во время компиляции и, следовательно, n должен быть известен во время компиляции (если только расширение, специфичное для компилятора, не позволяет выделять во время выполнения, но массив все еще находится в стеке).

int *array = new int[n] выделяет массив динамической длины в куче во время выполнения, поэтому n не нужно знать во время компиляции.

Ответ 4

Единственный правильный ответ на ваш вопрос: , потому что стандарт говорит так.

В отличие от C99, С++ никогда не потрудился указывать массивы переменной длины (VLAs), поэтому единственный способ получить массивы с переменным размером - это использовать динамическое распределение с помощью malloc, new или другого менеджера памяти.

Справедливость к С++, когда распределение стеков по размеру времени исполнения немного усложняет разворачивание стека, что также делает обработку исключений для функций, использующих эту функцию, более утомительной.

В любом случае, даже если ваш компилятор предоставляет эту C99-функцию в качестве расширения, неплохо всегда поддерживать очень жесткую привязку к вашему использованию стека:
Невозможно оправиться от разрыва стека, а ошибка - просто оставить Undefined Поведение по какой-либо причине.

Самый простой способ моделирования VLA на С++, хотя и без повышения производительности, избегая динамического распределения (и опасности прерываемости):

unique_ptr<T[]> array{new T[n]};

Ответ 5

Нет, второй не объявляет массив. Он использует форму массива operator new, и это, в частности, позволяет изменять значение первого измерения.

Ответ 6

Потому что он имеет другую семантику:

Если n - константа времени компиляции (в отличие от вашего примера):

int array[n]; //valid iff n is compile-time constant, space known at compile-time

Но учтите, что n - это значение времени выполнения:

int array[n]; //Cannot have a static array with a runtime value in C++
int * array = new int[n]; //This works because it happens at run-time, 
                          // not at compile-time! Different semantics, similar syntax.

В C99 вы можете иметь время выполнения n для массива, и пространство будет выполняться в стеке во время выполнения. Есть несколько предложений для подобных расширений в С++, но ни один из них не входит в стандарт.

Ответ 7

В выражении

new int[n]

int[n] не является типом. С++ обрабатывает "new с массивами" и "new с не-массивами" по-разному. Стандартная черновик N3337 имеет это примерно new:

Когда выделенным объектом является массив (т.е. используется синтаксис noptr-new-declarator или идентификатор нового типа или идентификатор типа обозначает тип массива), новое выражение дает указатель на начальный элемент (если есть) массива.

noptr-new-declarator ссылается на этот особый случай (оцените n и создайте массив этого размера), см.

noptr-нового-описателя:

    [выражение] attribute-specifier-seq opt

    noptr-new-declarator [константное выражение] attribute-specifier-seq opt

Однако вы не можете использовать это в "обычных" объявлениях, например

int array[n];

или в typedef

typedef int variable_array[n];

Это отличается от VLA C99, где оба разрешены.

Должен ли я использовать векторы вместо этого?

Да, вам нужно. Вы должны использовать векторы все время, если у вас нет очень сильной причины делать иначе (был один раз за последние 7 лет, когда я использовал new - когда я реализовал vector для школьного задания).

Ответ 8

Вы можете выделять память статически на stack или динамически на heap.

В вашем первом случае ваша функция содержит объявление массива с возможной переменной длины, но это невозможно, так как необработанные массивы должны иметь фиксированный размер во время компиляции, потому что они выделены в стеке. По этой причине их размер должен быть указан как константа, например 5. У вас может быть что-то вроде этого:

foo(){
    int array[5]; // raw array with fixed size 5
}

С помощью указателей вы можете указать размер переменной для памяти, которая будет указана, так как эта память будет динамически распределяться по куче. В вашем втором случае вы используете параметр n, чтобы указать пространство памяти, которое будет выделено.

В заключение мы можем сказать, что указатели не являются массивами: выделенная память с использованием указателя выделяется в куче, тогда как память, выделенная для необработанного массива, выделяется в стеке.

Существуют хорошие альтернативы сырым массивам, например стандартный контейнер vector, который представляет собой контейнер с переменной длиной.

Убедитесь, что вы хорошо понимаете разницу между распределением динамической и статической памяти, разностью между памятью, выделенной в стеке, и памятью, выделенной в куче.

Ответ 9

Это связано с тем, что язык С++ не имеет функции C, введенной в C99, известной как "массивы переменной длины" (VLA).

С++ отстает в использовании этой функции C, потому что тип std::vector из своей библиотеки удовлетворяет большинству требований.

Кроме того, в 2011 году стандарт C отскочил и сделал VLA дополнительной функцией.

VLA, в двух словах, позволяет использовать значение времени выполнения для определения размера локального массива, который выделяется в автоматическом хранилище:

int func(int variable)
{
   long array[variable]; // VLA feature

   // loop over array

   for (size_t i = 0; i < sizeof array / sizeof array[0]; i++) {
     // the above sizeof is also a VLA feature: it yields the dynamic
     // size of the array, and so is not a compile-time constant,
     // unlike other uses of sizeof!
   } 
}

VLA существовала на диалекте GNU C задолго до C99. В диалектах C без VLA размеры массива в объявлении должны быть постоянными выражениями.

Даже в диалектах C с VLA, только определенные массивы могут быть VLA. Например, статических массивов быть не может, и не могут быть динамические массивы (например, массивы внутри структуры, даже если экземпляры этой структуры распределены динамически).

В любом случае, так как вы кодирования в С++, это спорный вопрос!

Обратите внимание, что память, выделенная с помощью operator new, не является функцией VLA. Это специальный синтаксис С++ для динамического выделения, который возвращает тип указателя, как вы знаете:

int *p = new int[variable];

В отличие от VLA, этот объект будет сохраняться до тех пор, пока он не будет явно уничтожен с помощью delete [] и может быть возвращен из окружения.