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

#c #arrays #dynamic-data

#c #массивы #динамические данные

Вопрос:

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

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

Насколько я понимаю, стек (-сегмент) части сегмента данных и, следовательно, он не имеет «постоянного размера».

Комментарии:

1. Я не думаю, что есть какая-либо техническая причина, поскольку C99 добавил эту возможность.

2. @MarkRansom: В основном, это сложно сделать, и у нас уже есть std::vector , поэтому для этого не требовались компиляторы. С другой стороны, gcc и msvc все равно могут это сделать.

3. Может быть, потому, что исторически приложения имели меньше памяти, выделенной для стеков. Также стеки были фиксированной структурой, содержащей локальные переменные, значения параметров и обратные адреса. Для платформ со стеками фиксированного размера эта функция может быть опасной.

4. @ThomasMatthews: Это объяснение не подходит ни для одного языка, поддерживающего рекурсию.

5. @ThomasMatthews, опасность никогда не останавливала добавление функций в C . Ожидается, что вы знаете, что делаете.

Ответ №1:

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

C предоставляет std::vector для реализации функциональности, аналогичной VLA.


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

Комментарии:

1. Это и alloca можно использовать с размещением new[] . Хотя в многомерных случаях это будет довольно запутанно.

Ответ №2:

Я попытаюсь объяснить это на примере:

Допустим, у вас есть эта функция:

 int myFunc() {
   int n = 16;
   int arr[n];
   int k = 1;
}
  

Когда программа запускается, она устанавливает переменные таким образом в стек:

 - n @relative addr 0
- arr[16] @relative addr 4
- k @relative addr 64
TOTAL SIZE: 68 bytes
  

Допустим, я хочу изменить размер arr до 4 элементов. Я собираюсь сделать:

 delete arr;
arr = new int[4];
  

Теперь: если я оставлю стек таким образом, в стеке будут дыры неиспользуемого пространства. Итак, самое разумное, что можно сделать, это переместить все переменные из одного места в другое в стеке и пересчитать их позиции. Но нам чего-то не хватает: C не устанавливает позиции на лету, это делается только один раз, при компиляции программы. Почему? Это просто: потому что нет реальной необходимости иметь объекты переменного размера в стеке и потому, что их наличие замедлит работу всех программ при выделении / перераспределении пространства стека.

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

Комментарии:

1. Что вы имеете в виду, что собираетесь делать delete arr; ? Кто когда-либо говорил, что это будет допустимо? arr не является указателем.

2. arr НЕ является указателем, но это был бы единственный (на данный момент известный) способ его нераспределения. Это просто теоретический способ объяснить, как это будет работать.

3. Мы говорим о VLA C99, они четко определены; никаких гипотез не требуется. arr автоматически выделяется.

4. Абсолютно нет. Мы говорим о том, почему они не реализованы как стандарт. И C99 VLAS в любом случае даже не упоминаются в вопросе.

Ответ №3:

Обратите внимание, что предложение было отклонено, и следующее больше не соответствует действительности. Однако он может быть восстановлен для будущей версии C .

VLA, описанный в N3639, был принят на собрании в Бристоле и станет частью C 14, а также частью библиотеки «dynarray». Итак, используя компилятор с поддержкой C 14, мы можем начать писать что-то вроде:

 void func(int n)
{
    int arr[n];
}
  

Или используйте dynarray:

 #include <dynarray>

void func(int n)
{
    std::dynarray<int> arr(n);
}
  

Комментарии:

1. Примечание: позже он не был принят и не попал в C 14.

2. Пожалуйста, удалите этот ответ, он (в конечном итоге) неверен.

Ответ №4:

Простой ответ: потому что он не определен в стандарте C .

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

Комментарии:

1. И GCC, и MSVC реализуют это, но вы правы, что я слышал жалобы от обоих.

2. @MooingDuck: gcc.gnu.org/gcc-4.4/c99status . html , похоже, указывает на то, что для C99 это было непросто. И с такими вещами, как dtor, исключения и еще много чего, я считаю, что это довольно сложно для C . Если бы это стало стандартным, многим другим пришлось бы его реализовывать. Я не думаю, что многие люди хотят второго export . ИМХО, есть много более важных вещей, которые нужно изменить на будущее, чем VLAS, которые являются приятной функцией, но не незаменимы.

Ответ №5:

Стеки довольно малы, и их размеры могут сильно различаться в зависимости от архитектуры. Проблема в том, что довольно легко «перераспределить» и вызвать ошибку seg или записать в память, принадлежащую кому-то другому. Между тем, решения проблемы (например vector ) существуют уже давно.

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

Ответ №6:

Потому что в C статический массив нуждается в статическом постоянном размере, поэтому он не разрешен языком. Обратите внимание, что C99 поддерживает переменные массивы в стеке, а некоторые реализации поддерживают его в C , а также в качестве расширения.

Комментарии:

1. Правильно, вопрос в том, почему это так в C . Почему они не последовали идее C99 и не добавили их?

2. Я думаю, вы на правильном пути. Я не могу это доказать, но я считаю, что по разным причинам всем объектам в C нужен постоянный размер. Хотя почему?

3. @BenjaminLindley: постоянный или ненулевой размер? Вы путаете два?

4. @Als: Constant. Одна из причин, по которой я думаю, это потому, что я понятия не имею, чего можно было бы ожидать, если бы VLA был передан следующей функции: template<int N> void foo(int (amp;arr)[N])

5. @BenjaminLindley: Ах, хорошо, было бы интересно узнать, сможем ли мы найти больше контекста об этом в Стандарте.

Ответ №7:

Потому что так сказано в спецификации языка. Остальное не имеет значения (и объяснение с помощью сегментов ужасно неправильно по разным причинам).

Комментарии:

1. @user695652 Для начала, потому что в C нет сегментов.

2. Я нахожу этот ответ интеллектуально ленивым. Это не значит, что стандарт просто возник из ничего, люди работали над ним. Почему эти люди не пошли по пути VLAS C99?

3. @GMan: Ну, следовать по маршруту C99 VLAS в C 98 кажется непростым по причине 98 < 99 😉 Я думаю, что для заинтересованных было бы интересно прочитать все рассылки и предложения в процессе стандартизации C 98; Я не уверен, но я думаю, что люди что-то предложили, в качестве термина для поиска попробуйте «dynarray». Но не верьте мне на слово, слабая память, вы знаете…

4. @GMan Это не лень. Это догматизм. Это другое!

5. @PlasmaHH: Я имел в виду, что существует стандарт 03, а также новый стандарт 11.