Почему нельзя разместить в стеке массив произвольного размера?

Почему я не могу написать следующее?

char acBuf[nSize];

Только для того, чтобы стек не зарастал? Или есть возможность сделать что-то подобное, если я могу гарантировать, что всегда занимаю всего несколько сотен килобайт?

Насколько мне известно, std::string использует память своих членов для хранения назначенных строк, если они не превышают 15 символов. Только если строки длиннее, он использует эту память для хранения адреса некоторой памяти, выделенной в куче, которая затем берет данные.

Похоже, во время компиляции должно быть на 100 % определено, как стек будет выровнен во время выполнения. Это правда? Почему это?


person Allgaeuer    schedule 11.10.2016    source источник
comment
Да это правда. C++ не поддерживает VLA.   -  person Hatted Rooster    schedule 11.10.2016
comment
Если вы выделяете стек, (теоретически) вся память для каждого кадра стека выделяется в начале его области, поэтому у нас есть динамическое хранилище, где память выделяется с использованием свободного хранилища.   -  person George    schedule 11.10.2016


Ответы (7)


Это не имеет ничего общего с предотвращением переполнения стека, вы можете просто переполнить стек с помощью char a[SOME_LARGE_CONSTANT]. В C++ размер массива должен быть известен во время компиляции, это, среди прочего, необходимо для вычисления размера структур, содержащих массивы.

C, с другой стороны, имел массивы переменной длины с C99, что добавляет исключение и позволяет размер, зависящий от времени выполнения, для массивов в пределах области действия функции. Почему в С++ этого нет? Он никогда не был принят стандартом C++.

person josefx    schedule 11.10.2016
comment
Но когда я следую этому: stackoverflow.com/a/5770919/1594594 я все еще не могу использовать VLA :( - person Allgaeuer; 11.10.2016
comment
@Allgaeuer Реализация Microsoft Visual Studio C не поддерживает VLA. - person Banex; 11.10.2016
comment
@Allgaeuer, в вашей ссылке упоминается ANSI C, то есть C90. Microsoft никогда не предоставляла полную реализацию C99, я думаю, что они поддерживают только подмножество, необходимое для C++. В последней ссылке в этом ответе также упоминается, что VLA не поддерживаются. - person josefx; 11.10.2016

Почему я не могу написать следующее?

char acBuf[nSize];

Они называются массивами переменной длины (VLA) и не поддерживаются C++. Причина в том, что стек очень быстрый, но крошечный по сравнению с бесплатным магазином (куча, по вашим словам). Это означает, что в любой момент, когда вы добавляете много элементов в VLA, ваш стек может просто переполниться, и вы получите неопределенное исключение времени выполнения. Это также может произойти с массивами стека размером во время компиляции, но их легче поймать, потому что поведение программы не влияет на их размер. Это означает, что x не обязательно должно происходить после y, чтобы создать переполнение стека, это просто сразу же. Это описывает это более подробно и яростно.

Контейнеры, такие как std::vector, используют свободное хранилище, которое намного больше и имеет способ справиться с чрезмерным выделением (выдает bad_alloc).

person Hatted Rooster    schedule 11.10.2016
comment
Я собирался изменить детализацию и ярость на детализацию и диапазон, но после прочтения связанного обсуждения не нашел опечатки, которую можно было бы исправить. VLA обязательно вызывают горячие дискуссии! - person tucuxi; 11.10.2016

В отличие от C, C++ не поддерживает массивы переменной длины. Если они вам нужны, вы можете использовать нестандартные расширения, такие как alloca или расширения GNU (поддерживается clang и GCC). У них есть свои предостережения, поэтому обязательно прочитайте руководство, чтобы убедиться, что вы используете их безопасно.

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

person rightfold    schedule 11.10.2016
comment
Но когда я следую этому: stackoverflow.com/a/5770919/1594594 я все еще не могу использовать VLA :( - person Allgaeuer; 11.10.2016

Мой совет — взглянуть на alloca.h

   void *alloca(size_t size);

Функция alloca() выделяет size байтов пространства в кадре стека вызывающего объекта. Это временное пространство автоматически освобождается, когда функция, вызвавшая alloca(), возвращается к вызывающей стороне.

person user3322793    schedule 11.10.2016

Одна возможная проблема, которую я вижу с VLA в C++, — это тип.

Какой тип acBuf в char acBuf[nSize] или еще хуже в char acBuf[nSize][nSize] ?

template <typename T> void foo(const T&);

void foo(int n)
{
    char mat[n][n];

    foo(mat);
}

Вы не можете передать этот массив по ссылке на

template <typename T, std::size_t N>
void foo_on_array(const T (&a)[N]);
person Jarod42    schedule 11.10.2016

Вы должны быть довольны тем, что стандарт C++ не одобряет опасную практику (массивы переменной длины в стеке) и вместо этого поощряет менее опасную практику (массивы переменной длины и std::vector с выделением кучи).

Массивы переменной длины в стеке более опасны, потому что:

  • Доступное пространство стека обычно составляет 8 МБ, что намного меньше, чем 2 ГБ (или более) доступного пространства кучи.
  • Когда пространство стека исчерпано, программа аварийно завершает работу с SIGSEGV, и для нее требуется специальное программное обеспечение, такое как GNU libsigsegv оправиться от такой ситуации.
  • В типичных программах на C++ программист не знает, будет ли длина массива определенно оставаться ниже предела, такого как 4 МБ.
person Bruno Haible    schedule 14.10.2016

Почему я не могу написать следующее? char acBuf[nSize];

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

Если вы хотите создать динамический массив, вы должны использовать оператор new, чтобы он был сохранен в куче.

char *acBuf = new char[nsize];
person Ronaldo Umaña    schedule 17.10.2016