Почему массивы переменной длины не являются частью стандарта C ++?

Я не очень много использовал Си в последние несколько лет. Когда я читаю это question сегодня я наткнулся на синтаксис языка C, с которым я не был знаком.

По-видимому, в C99 допустим следующий синтаксис:

void foo(int n) {
    int values[n]; //Declare a variable length array
}

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

Некоторые возможные причины:

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

Стандарт C ++ гласит, что размер массива должен быть постоянным выражением (8.3.4.1).

Да, конечно, я понимаю, что в игрушечном примере можно использовать std::vector<int> values(m);, но это выделяет память из кучи, а не из стека. И если мне нужен многомерный массив, например:

void foo(int x, int y, int z) {
    int values[x][y][z]; // Declare a variable length array
}

версия vector становится довольно неуклюжей:

void foo(int x, int y, int z) {
    vector< vector< vector<int> > > values( /* Really painful expression here. */);
}

Срезы, строки и столбцы также потенциально могут быть распределены по всей памяти.

Глядя на обсуждение на comp.std.c++, становится ясно, что этот вопрос является довольно спорным с некоторыми очень тяжеловесными именами по обе стороны аргумента. Конечно, не очевидно, что std::vector всегда является лучшим решением.


person Andreas Brinck    schedule 11.12.2009    source источник
comment
Просто из любопытства, зачем его нужно размещать в стеке? Вы не боитесь проблем с производительностью выделения кучи?   -  person Dimitri C.    schedule 11.12.2009
comment
@Dimitri Не совсем, но нельзя отрицать, что выделение стека будет быстрее, чем выделение кучи. А в некоторых случаях это может иметь значение.   -  person Andreas Brinck    schedule 11.12.2009
comment
Основное преимущество массивов переменной длины в том, что все данные расположены близко друг к другу, поэтому при итерации по этому массиву вы читаете и записываете байты рядом друг с другом. Ваши данные загружаются в кеш, и процессор может работать с ними, не извлекая и не отправляя байты в / из памяти.   -  person Calmarius    schedule 24.10.2010
comment
Массивы переменной длины также могут использоваться для замены констант препроцессора статическими константными переменными. Также в C у вас нет других вариантов для VLA, и иногда необходимо писать переносимый код C / C ++ (совместимый с обоими компиляторами).   -  person Yury    schedule 21.12.2012
comment
Кроме того, похоже, что clang ++ разрешает VLA.   -  person user3426763    schedule 17.03.2014
comment
Несправедливое сравнение, поскольку для действительно болезненного выражения здесь у вас нет эквивалента в версии C. Попробуйте написать инициализатор для VLA (подсказка: невозможно)   -  person M.M    schedule 17.03.2014
comment
Пока тип легко конструируемый и дешевый для построения (как примитив), вы можете определить разумный максимум, а затем выделить массив фиксированного размера. Затем просто работайте в диапазоне [0, n).   -  person Anthony    schedule 22.04.2014
comment
Они были предложены для C ++ 14 как массивы размера выполнения (которые должны быть размещены в стеке и имели некоторые отличия от C VLA) вместе с шаблоном класса dynarray (std::array в их необработанном массиве), но оба были проголосованы аут (причем последний переводится в TS). По-видимому, dynarray должен был сочетаться со специальной магией компилятора, чтобы при использовании в стеке его можно было оптимизировать так, чтобы он был таким же эффективным, как массив размера времени выполнения (по крайней мере, на платформах с традиционной настройкой стека и кучи. ). Я не совсем разбираюсь в деталях.   -  person Justin Time - Reinstate Monica    schedule 05.02.2017
comment
Clang поддерживает класс как std::experimental::dynarray в заголовке <experimental/dynarray> как часть libc++. И Clang, и GCC поддерживают VLA на C, причем GCC (но не Clang) также позволяет их инициализировать; обратите внимание, что они идут с ограничением C, что sizeof оценивается во время выполнения для VLA.   -  person Justin Time - Reinstate Monica    schedule 05.02.2017
comment
Что касается GCC и clang: моя рекомендация - -Werror=vla (GCC: включить в файл спецификации).   -  person Aconcagua    schedule 10.04.2019
comment
Существует альтернатива использованию alloca () или _alloca () для выделения памяти из стека, которая автоматически освобождается при возврате функции.   -  person rcgldr    schedule 30.09.2020
comment
Кажется, только -pedantic выдаст предупреждение об их использовании в GCC / Clang.   -  person user2023370    schedule 09.02.2021


Ответы (12)


Недавно в usenet началось обсуждение этого вопроса: Почему нет VLA в C ++ 0x.

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

C99 VLA может дать небольшое преимущество в виде возможности создавать небольшие массивы без потери места или вызова конструкторов для неиспользуемых элементов, но они внесут довольно большие изменения в систему типов (вам нужно иметь возможность указывать типы в зависимости от значений времени выполнения - это еще не существует в текущем C ++, за исключением спецификаторов типа оператора new, но они обрабатываются особым образом, так что время выполнения не выходит за пределы области действия оператора new).

Вы можете использовать std::vector, но это не совсем то же самое, поскольку он использует динамическую память, и заставить его использовать собственный стек-распределитель не совсем просто (выравнивание также является проблемой). Это также не решает ту же проблему, потому что вектор - это контейнер с изменяемым размером, тогда как VLA имеют фиксированный размер. Предложение C ++ Dynamic Array предназначено представить решение на основе библиотеки в качестве альтернативы VLA на основе языка. Однако, насколько мне известно, он не будет частью C ++ 0x.

person Johannes Schaub - litb    schedule 11.12.2009
comment
+1 и принято. Одно замечание: я думаю, что аргумент безопасности немного слабоват, поскольку существует множество других способов вызвать переполнение стека. Аргумент безопасности можно использовать для поддержки позиции, что вы никогда не должны использовать рекурсию и что вы должны выделять все объекты из кучи. - person Andreas Brinck; 11.12.2009
comment
Итак, вы говорите, что, поскольку есть другие способы вызвать переполнение стека, мы могли бы поощрять их больше? - person jalf; 11.12.2009
comment
@Andreas, согласился насчет слабости. Но для рекурсии требуется огромное количество вызовов, пока стек не будет съеден, и если это может быть так, люди будут использовать итерацию. Однако, как говорят некоторые люди в потоке usenet, это не аргумент против VLA во всех случаях, поскольку иногда вы определенно можете знать верхнюю границу. Но в этих случаях из того, что я вижу, может быть достаточно статического массива, поскольку он в любом случае не будет тратить много места (если он будет, тогда вам действительно придется спросить, велика ли область стека хватит еще раз). - person Johannes Schaub - litb; 11.12.2009
comment
Также посмотрите ответ Мэтта Остерна в этом потоке: спецификация языка VLA, вероятно, будет значительно более сложной для C ++ из-за более строгого соответствия типов в C ++ (пример: C позволяет назначать T(*)[] T(*)[N] - в C ++ это запрещено, поскольку C ++ не знает о совместимости типов - он требует точных совпадений), параметров типа, исключений, конструкторов и деструкторов и прочего. Я не уверен, действительно ли выгода от VLA окупит всю эту работу. Но с другой стороны, я никогда не использовал VLA в реальной жизни, поэтому я, вероятно, не знаю хороших вариантов их использования. - person Johannes Schaub - litb; 11.12.2009
comment
@Johannes: вероятно, в некоторых случаях использование VLA преодолевает ваши возражения. Например, рекурсивная функция, на каждом шаге i требует N_i места. Единственный верхний предел, который у меня есть, S, - это сумма всех N_i, а не отдельных N_i. Так что я могу знать, что (а) S достаточно мал для моего стека, поэтому VLA не переполняется, но (б) S * максимальная глубина рекурсии слишком велика для моего стека. Конечно, это не оправдывает использования сложной языковой функции: я должен просто выделить S в стеке в верхней части рекурсии и передать указатель на то, что осталось неиспользованным. - person Steve Jessop; 11.12.2009
comment
А, теперь я вижу, что Каз Кюлхеку уже приводил этот пример в ветке com.std.c ++. Пример строки Windows, приведенный впоследствии Альфом, на самом деле кажется мне неправильным, поскольку, на мой вкус, он возлагает слишком большую ответственность на вызывающего узкую строковую функцию, чтобы гарантировать, что для библиотеки осталось достаточно стека для использования VLA. VLA не делают то, что вы действительно хотите в такой ситуации преобразования строк, а именно выделяют в стеке, если есть место, и из кучи, если его нет. Но я думаю, что в Windows есть место в стеке, если оно есть в куче, в значительной степени. - person Steve Jessop; 13.12.2009
comment
@Johannes: возможность создавать небольшие массивы без потери места или вызова конструкторов для неиспользуемых элементов. Контейнер, подобный boost :: array, который принимает параметр времени выполнения, указывающий, сколько элементов для инициализации распределения, будет столь же полезным (за исключением, возможно, рекурсивных алгоритмов). Любой массив размером во время выполнения должен иметь максимальное количество элементов, и немедленное выделение этого количества места в стеке даст вам неизбежный сбой раньше, и это хорошо. - person Viktor Sehr; 16.12.2009
comment
@Viktor, действительно. Но массив максимального размера также вызовет все конструкторы, что может быть не тем, что вам нужно. Я думаю, что контейнер, подобный массиву, является хорошим компромиссом между изменением языка для введения VLA (что я считаю большим изменением) и отсутствием поддержки вообще. - person Johannes Schaub - litb; 17.12.2009
comment
@Johannes: Нет, я имею в виду; если массив является оболочкой вокруг примитивного типа (например, unsigned char [N * sizeof (T)]), массив может вызывать новый / конструктор размещения для количества элементов в переменной размера времени выполнения. (если N обозначает статический максимальный размер, T - тип и n - размер времени выполнения, ваш конструктор делает что-то вроде for (i ... n) {new (this + sizof (T) * i) T ();} - person Viktor Sehr; 17.12.2009
comment
@Viktor, я согласен. Это было бы полезно, и вы могли бы указать N в качестве параметра шаблона как максимальное количество элементов. И писать это будет проще на C ++ 0x с различными доступными инструментами выравнивания (alignof и т. Д.). - person Johannes Schaub - litb; 17.12.2009
comment
Основная причина, по которой мне нужны VLA, заключается в том, что они очень хорошо служат для оценки полиномов произвольной степени - мне нужна скорость выделения стека, а значение n не будет очень высоким - и обычно 2-4, поэтому установка произвольного максимума 20 кажется расточительной. В настоящее время я делаю это с помощью беспорядочного шаблонного решения, но VLA подошли бы гораздо более чисто. - person AHelps; 10.04.2015
comment
@AHelps: Возможно, для этого лучше всего подошел бы тип, который ведет себя как vector, но требует фиксированного шаблона использования LIFO и поддерживает один или несколько статически распределенных буферов для каждого потока, размер которых обычно соответствует наибольшему общему распределению потока когда-либо использовался, но может быть явно обрезан. Обычное распределение в общем случае не требует ничего, кроме копирования указателя, вычитания указателя из указателя, целочисленного сравнения и добавления указателя; для отмены выделения просто потребуется копия указателя. Не намного медленнее, чем VLA. - person supercat; 03.06.2015
comment
Кстати, в C ++ я рекомендую small_vector глупости или ускорения (github.com / facebook / folly / blob / master / folly / docs /). Версия Folly позволяет вам также запретить использование кучи. Это решает проблемы с ненужными вызовами конструктора с помощью подхода с достаточно большим массивом стека. - person Johannes Schaub - litb; 03.06.2015
comment
@AndreasBrinck Хорошо сказано. Это не способ С ++, позволяющий программистам решать, что использовать, и безопасно их использовать. - person John Z. Li; 12.09.2018
comment
@ JohannesSchaub-litb stackoverflow.com/q/67405946/11862989, пожалуйста, ответьте на этот вопрос. это связано только с этим, но в контексте динамического массива. - person Abhishek Mane; 06.05.2021
comment
Странный аргумент, чтобы утверждать, что незнание размера заранее означает небезопасный код. - person Devolus; 06.05.2021
comment
@Devolus имелось в виду, что если вы не знаете верхней границы. - person Johannes Schaub - litb; 09.05.2021
comment
Я думаю, что утилита, которая статически выделяет фиксированный объем памяти в стеке и вызывает конструктор только для некоторых элементов, не, конечно, будет небезопасной. - person Johannes Schaub - litb; 09.05.2021

(Предыстория: у меня есть некоторый опыт внедрения компиляторов C и C ++.)

Массивы переменной длины в C99 были ошибкой. Чтобы поддерживать VLA, C99 пришлось пойти на следующие уступки здравому смыслу:

  • sizeof x больше не всегда является константой времени компиляции; компилятор иногда должен генерировать код для оценки sizeof-выражения во время выполнения.

  • Разрешение двумерных VLA (int A[x][y]) потребовало нового синтаксиса для объявления функций, которые принимают 2D VLA в качестве параметров: void foo(int n, int A[][*]).

  • Менее важно в мире C ++, но чрезвычайно важно для целевой аудитории C программистов встроенных систем, объявление VLA означает перебор произвольно большого фрагмента вашего стека. Это гарантированное переполнение стека и сбой. (Каждый раз, когда вы объявляете int A[n], вы неявно утверждаете, что у вас есть 2 ГБ свободного стека. В конце концов, если вы знаете, что «n здесь определенно меньше 1000», вы просто объявляете int A[1000]. Подставляя 32-битное целое число n ибо 1000 - это признание того, что вы понятия не имеете, каким должно быть поведение вашей программы.)

Хорошо, теперь перейдем к разговору о C ++. В C ++ существует такое же сильное различие между «системой типов» и «системой ценностей», что и в C89… но мы действительно начали полагаться на нее так, как этого не делал C. Например:

template<typename T> struct S { ... };
int A[n];
S<decltype(A)> s;  // equivalently, S<int[n]> s;

Если бы n не был константой времени компиляции (то есть, если бы A был переменно изменяемого типа), тогда что же, черт возьми, было бы типом S? Будет ли тип S также определяться только во время выполнения?

Как насчет этого:

template<typename T> bool myfunc(T& t1, T& t2) { ... };
int A1[n1], A2[n2];
myfunc(A1, A2);

Компилятор должен сгенерировать код для некоторого экземпляра myfunc. Как должен выглядеть этот код? Как мы можем статически сгенерировать этот код, если мы не знаем тип A1 во время компиляции?

Хуже того, что, если во время выполнения окажется, что n1 != n2, так что !std::is_same<decltype(A1), decltype(A2)>()? В этом случае вызов myfunc не должен даже компилироваться, потому что определение типа шаблона не сработает! Как мы могли бы эмулировать такое поведение во время выполнения?

По сути, C ++ движется в направлении принятия все большего и большего числа решений в время компиляции: генерация кода шаблона, constexpr оценка функции и так далее. Между тем, C99 был занят переносом традиционных решений времени компиляции (например, sizeof) в среду выполнения. Имея это в виду, действительно ли имеет смысл прилагать какие-либо усилия, пытаясь интегрировать VLA в стиле C99 в C ++?

Как уже отмечал каждый другой ответчик, C ++ предоставляет множество механизмов распределения кучи (std::unique_ptr<int[]> A = new int[n]; или std::vector<int> A(n); - очевидные), когда вы действительно хотите передать идею: «Я не знаю, сколько оперативной памяти мне может понадобиться». А C ++ предоставляет изящную модель обработки исключений для решения неизбежной ситуации, когда объем необходимой вам оперативной памяти превышает объем имеющейся у вас оперативной памяти. Но, надеюсь, этот ответ дает вам хорошее представление о том, почему VLA в стиле C99 не хорошо подходят для C ++ - и даже не подходят для C99. ;)


Для получения дополнительной информации по теме см. N3810 "Альтернативы для Array Extensions », статья Бьярна Страуструпа по VLA в октябре 2013 года. Точка зрения Бьярна сильно отличается от моей; N3810 больше фокусируется на поиске хорошего синтаксиса C ++ для вещей и на недопущении использования необработанных массивов в C ++, тогда как я больше сосредоточился на последствиях для метапрограммирования и системы типов. Я не знаю, считает ли он последствия метапрограммирования / типизации решенными, разрешимыми или просто неинтересными.


Хорошая запись в блоге, затрагивающая многие из этих вопросов, - «Законное использование массивов переменной длины» (Крис Веллонс, 27.10.2019).

person Quuxplusone    schedule 03.02.2014
comment
Я согласен, что VLA ошибались. Вместо этого, гораздо более широко реализованный и гораздо более полезный alloca() должен был быть стандартизирован в C99. VLA - это то, что происходит, когда комитет по стандартам опережает реализации, а не наоборот. - person MadScientist; 25.03.2014
comment
Я не понимал, что C допускает VLA в качестве параметров функции. Я согласен (чего бы это ни стоило, поскольку я не пишу компиляторы), что это довольно пугающе. С другой стороны, VLA как локальные переменные кажутся мне разумными. Конечно, вы можете переполнить свой стек, но вы должны думать об этом каждый раз, когда используете локальные переменные, поэтому в этом нет ничего нового. А размер - разве это не цена за функцию? - person michaeljt; 26.09.2014
comment
Система изменяемых типов - отличное дополнение IMO, и ни один из ваших пунктов не противоречит здравому смыслу. (1) стандарт C не различает время компиляции и время выполнения, так что это не проблема; (2) * не является обязательным, вы можете (и должны) писать int A[][n]; (3) Вы можете использовать систему типов без фактического объявления каких-либо VLA. Например, функция может принимать массив изменяемого типа, и ее можно вызывать с не-VLA 2-мерными массивами разных размеров. Однако вы делаете обоснованные выводы в последней части вашего сообщения. - person M.M; 17.03.2015
comment
аргумент типа становится особенно очевидным при переходе к gsl :: span ‹›. VLA (поскольку g ++ поддерживает их как расширения c ++) не имеет смысла при передаче через шаблон, пытаясь определить его размер. - person Arvid; 28.04.2016
comment
Вы знаете, все те полезные вещи, которые вы описали, продолжали бы существовать, даже если бы поддерживались VLA - они просто не компилировались бы для VLA, только для массивов фиксированной длины. Не то чтобы я не согласен с вашими оговорками о VLA, просто они больше связаны с использованием, чем с наличием этой функции. Выполнение reinterpret_cast из void * для указателя функции также означает неожиданное поведение и возможное переполнение стека, но это не значит, что это не должно быть возможным. - person einpoklum; 11.05.2016
comment
объявление VLA означает перебор произвольно большой части вашего стека. Это гарантированное переполнение стека и сбой. (Каждый раз, когда вы объявляете int A [n], вы неявно утверждаете, что у вас есть 2 ГБ свободного стека, это эмпирически неверно. Я только что запустил программу VLA со стеком намного меньше 2 ГБ без какого-либо переполнения стека. - person Jeff Hammond; 31.05.2016
comment
@Jeff: Какое было максимальное значение n в вашем тестовом примере и каков был размер вашего стека? Я предлагаю вам попробовать ввести значение n не меньше размера вашего стека. (И если у пользователя нет возможности контролировать значение n в вашей программе, я предлагаю вам просто передать максимальное значение n прямо в объявление: объявить int A[1000] или что-то еще, что вам нужно. VLA только необходимы, и только опасно, когда максимальное значение n не ограничено какой-либо небольшой константой времени компиляции.) - person Quuxplusone; 01.06.2016
comment
По моему опыту, основная причина, по которой мне нравится VLA, заключается в том, что я могу индексировать двухмерные массивы, такие как массивы a[2][3] и memcpy(&a[1][2], ptr). Меня никогда особо не волновало, находится ли эта память в стеке или в куче. Если бы существовал способ сгенерировать view в некоторый буфер как матрицу произвольной формы и получить к нему доступ как к массиву, это было бы всем, что я хотел бы от VLA. Это означает, что класс получает указатель и предоставляет операторы [] и & только для синтаксического сахара. - person dashesy; 10.07.2016
comment
@MadScientist: стандарт должен был включать функцию распределения LIFO, внутреннее освобождение которой требует передачи указателя и размера более раннего распределения, пары встроенных функций mark / release, которые могут использовать что-то большее, чем указатель [как с va_list], где release будет очищать любые объекты, выделенные LIFO, начиная с соответствующей метки, и спецификация, согласно которой возврат из функции, в которой объекты были выделены LIFO, может освобождать объекты или нет, в зависимости от удобства реализации. - person supercat; 12.01.2017
comment
@MadScientist: Такая семантика может поддерживаться любой платформой [в отличие от семантики alloca], и разделение семантики отметки / выпуска позволяет реализациям, которые выполняют такое распределение в куче, для чистого восстановления хранилища после longjmp. - person supercat; 12.01.2017
comment
Поскольку alloca () может быть реализована с использованием таких встроенных функций, по определению верно, что alloca () может быть реализована на любой платформе как стандартная функция компилятора. Нет причин, по которым компилятор не мог обнаружить первый экземпляр alloca () и организовать встраивание типов меток и выпусков в код, и нет причин, по которым компилятор не может реализовать alloca () с использованием кучи, если это невозможно сделать со стеком. Что является жестким / непереносимым, так это то, что alloca () реализована поверх компилятора C, чтобы он работал с широким спектром компиляторов и операционных систем. - person MadScientist; 12.01.2017
comment
@MadScientist: Семантика, которую я описываю, может поддерживаться любым размещенным компилятором, который позволяет добавлять библиотеку и файл заголовка. Хотя размещенный компилятор для любой платформы мог бы обрабатывать alloca (), если было бы допустимо, чтобы longjmp отказывался от каких-либо проблем, созданных в функциях, которые выходят из них, я думаю, что было бы лучше определить семантику, которая может быть поддерживается существующими компиляторами и может сосуществовать с longjmp. - person supercat; 14.09.2018
comment
Между прочим, если кто-то хочет использовать соглашение о передаче аргументов размера массива после указателей на сами массивы, это можно сделать с использованием синтаксиса функции K & R1, но не с использованием современного синтаксиса. - person supercat; 14.09.2018
comment
Что ж, каким-то образом компиляторы C ++ решили эти проблемы, разрешив VLA в качестве расширения. Вы также упускаете момент, касающийся разницы между VLA небольшого размера и фиксированным массивом размером, равным максимальному размеру VLA - вам придется по умолчанию создавать все элементы для статического массива, а вы, возможно, не сможете! - person SergeyA; 10.04.2019
comment
@SergeyA: Интересный комментарий о том, что вы не можете [default-construct A]. Верно: в одном частном случае, когда длина A vla[n] динамически определяется как 0, нам вообще не нужно вызывать A::A(). Итак, должен ли компилятор требовать, чтобы A::A() существовал и мог быть вызван? Расширение Clang VLA говорит "да"; Расширение GCC говорит иногда. godbolt.org/z/DDW4Lx (и решение этих проблем см. godbolt.org/z/Guvxoo.) - person Quuxplusone; 11.04.2019
comment
'В конце концов, если вы знаете, что здесь n определенно меньше 1000, тогда вы бы просто объявили int A [1000].' - это просто чепуха. Если, скажем, длина VLA равна 10 на 99,99% вызовов функций и достигает своей верхней границы 1000 только на 0,01% вызовов, вы просто потратили впустую 1000 байтов, которые никогда не будут освобождены, пока кадр остается на stack - что может быть почти всегда, если функция находится на более высоком уровне иерархии потока управления. Вы можете подумать, что 1000 байт - это немного, но тогда учитывайте все промахи в кэше каждый раз, когда вашему процессору приходится входить и выходить из этой функции! - person Will; 19.04.2019
comment
Как объясняется в ссылке, которую вы предоставляете в этом ответе, создание VLA как необязательной функции было ошибочным. Выделение фактических объектов VLA - действительно довольно полезная функция, но использование указателей на VLA - серьезное улучшение языка C. Такие указатели имеют множество применений: более четкий многомерный malloc, типобезопасные вызовы функций с переданным размером и т. Д. И т. Д. Они улучшают безопасность типов и читаемость. - person Lundin; 19.03.2021
comment
Что касается встроенных, я работаю почти исключительно со встроенными системами и постоянно использую указатели на VLA. Однако размещение объектов VLA запрещено моим стандартом кодирования. Но я не припомню, чтобы когда-либо видел переполнение стека, вызванное VLA, в какой-либо встроенной системе. Движение против VLA, скорее, исходит от людей, занимающихся ПК, во главе которых стоит Microsoft. Потому что, если разрешить VLA, MS придется обновить свой так называемый компилятор 2019 с 1989 года, чтобы он соответствовал версии языка 1999 года. - person Lundin; 19.03.2021

Вы всегда можете использовать alloca () для выделения памяти в стеке во время выполнения, если хотите:

void foo (int n)
{
    int *values = (int *)alloca(sizeof(int) * n);
}

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

Краткое примечание: как упоминалось на странице руководства Mac OS X для alloca (3), «Функция alloca () зависит от машины и компилятора; ее использование не рекомендуется». Просто чтобы вы знали.

person PfhorSlayer    schedule 11.12.2009
comment
Кроме того, область действия alloca () - это вся функция, а не только блок кода, содержащий переменную. Таким образом, используя его внутри цикла, он будет постоянно увеличивать стек. У VLA такой проблемы нет. - person sashoalm; 01.07.2016
comment
Однако VLA, имеющие область действия включающего блока, означают, что они значительно менее полезны, чем alloca () с областью действия всей функции. Учтите: if (!p) { p = alloca(strlen(foo)+1); strcpy(p, foo); } Это невозможно сделать с VLA именно из-за их блочной области. - person MadScientist; 12.01.2017
comment
Это не отвечает на вопрос OP почему. Более того, это C-подобное решение, а не совсем C++. - person Adrian W; 25.06.2018

В своей собственной работе я понял, что каждый раз, когда мне нужно что-то вроде автоматических массивов переменной длины или alloca (), мне было все равно, что память физически расположена в стеке процессора, просто она поступала из некоторый распределитель стека, который не вызывает медленных обращений к общей куче. Итак, у меня есть объект для каждого потока, который владеет некоторой памятью, из которой он может выталкивать / извлекать буферы переменного размера. На некоторых платформах я разрешаю этому расширяться через mmu. Другие платформы имеют фиксированный размер (обычно вместе со стеком ЦП фиксированного размера, потому что нет mmu). Одна платформа, с которой я работаю (портативная игровая консоль), в любом случае имеет драгоценный небольшой стек ЦП, потому что он находится в скудной быстрой памяти.

Я не говорю, что никогда не нужно помещать буферы переменного размера в стек процессора. Честно говоря, я был удивлен, когда обнаружил, что это не стандарт, так как определенно кажется, что концепция достаточно хорошо вписывается в язык. Однако для меня требования «переменный размер» и «физическое размещение в стеке процессора» никогда не совпадали. Речь шла о скорости, поэтому я сделал свой собственный «параллельный стек для буферов данных».

person Eric    schedule 21.03.2013

Кажется, это будет доступно в C ++ 14:

https://en.wikipedia.org/wiki/C++14#Runtime-sized_one_dimensional_arrays

Обновление: оно не вошло в C ++ 14.

person Viktor Sehr    schedule 13.08.2013
comment
интересно. Херб Саттер обсуждает это здесь в разделе Динамические массивы: isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting (это ссылка на информацию из Википедии) - person Default; 13.08.2013
comment
Массивы времени выполнения и dynarray были перемещены в техническую спецификацию Array Extensions, о которой 18 января 2014 г. написано 78.86.152.103 в Википедии: en.wikipedia.org/w/ - person strager; 24.02.2014
comment
Википедия не является нормативным справочником :) Это предложение не вошло в C ++ 14. - person M.M; 04.11.2014
comment
@ViktorSehr: Каков статус этого w.r.t. С ++ 17? - person einpoklum; 11.05.2016
comment
@einpoklum Понятия не имею, используйте boost :: container :: static_vector - person Viktor Sehr; 02.03.2017
comment
@this: Нет: Arrays TS был убит в 2016 году. - person Davis Herring; 04.11.2019

Бывают ситуации, когда выделение памяти кучи очень дорого по сравнению с выполняемыми операциями. Примером является матричная математика. Если вы работаете с небольшими матрицами, скажем, от 5 до 10 элементов и выполняете много арифметических операций, накладные расходы malloc будут действительно значительными. В то же время создание константы времени компиляции для размера действительно кажется очень расточительным и негибким.

Я думаю, что C ++ настолько небезопасен сам по себе, что аргумент «старайтесь не добавлять больше небезопасных функций» не очень силен. С другой стороны, поскольку C ++, возможно, является наиболее эффективным языком программирования во время выполнения, функции, которые делают его еще более полезным, всегда полезны: люди, которые пишут программы, критичные к производительности, будут в значительной степени использовать C ++, и им нужна как можно большая производительность. Одна из таких возможностей - перемещение файлов из кучи в стек. Другое дело - уменьшение количества блоков кучи. Разрешение VLA в качестве членов объекта было бы одним из способов добиться этого. Я работаю над таким предложением. Правда, реализовать это немного сложно, но кажется вполне выполнимым.

person Bengt Gustafsson    schedule 22.01.2011

Это рассматривалось для включения в C ++ / 1x, , но было отброшено (это это поправка к тому, что я сказал ранее).

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

person philsquared    schedule 11.12.2009
comment
Нет, std :: vector не размещает данные в стеке. :) - person Kos; 10.08.2011
comment
стек - это деталь реализации; компилятор может выделять память откуда угодно, если соблюдаются гарантии времени жизни объекта. - person M.M; 17.03.2014
comment
@ M.M: Достаточно честно, но на практике мы все еще не можем использовать std::vector вместо, скажем, alloca(). - person einpoklum; 11.05.2016
comment
@einpoklum с точки зрения получения правильного вывода для вашей программы, вы можете. Производительность - это проблема качества реализации - person M.M; 11.05.2016
comment
Качество реализации @MM не переносимо. и если вам не нужна производительность, вы в первую очередь не используете c ++ - person pal; 09.08.2019

Используйте для этого std :: vector. Например:

std::vector<int> values;
values.resize(n);

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

person Dimitri C.    schedule 11.12.2009
comment
Основное применение массивов переменной длины - вычисление полиномов произвольной степени. В этом случае ваш небольшой недостаток производительности означает, что код в типичных случаях работает в пять раз медленнее. Это не мало. - person AHelps; 10.04.2015
comment
Почему бы вам просто не использовать std::vector<int> values(n);? Используя resize после построения, вы запрещаете неподвижные типы. - person L. F.; 06.10.2019

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

Кстати, на вопросы о том, "почему" стандарт C ++ такой, каков он есть, модерируемая группа новостей Usenet comp.std.c ++ - это то место, куда можно перейти.

person Community    schedule 11.12.2009
comment
-1 Вектор не всегда лучше. Часто да. Всегда нет. Если вам нужен только небольшой массив, вы находитесь на платформе, где пространство кучи является медленным, а реализация вектора в вашей библиотеке использует пространство кучи, тогда эта функция вполне могла бы быть лучше, если бы она существовала. - person Patrick M; 11.07.2013

C99 позволяет VLA. И это накладывает некоторые ограничения на то, как объявлять VLA. Подробнее см. 6.7.5.2 стандарта. C ++ запрещает VLA. Но g ++ это позволяет.

person Jingguo Yao    schedule 31.07.2012
comment
Можете ли вы предоставить ссылку на стандартный абзац, на который вы указываете? - person Vincent; 26.05.2017

Если вы знаете значение во время компиляции, вы можете сделать следующее:

template <int X>
void foo(void)
{
   int values[X];

}

Изменить: вы можете создать вектор, который использует распределитель стека (alloca), поскольку распределитель является параметром шаблона.

person Edouard A.    schedule 11.12.2009
comment
Если вы знаете значение во время компиляции, вам вообще не нужен шаблон. Просто используйте X прямо в своей нешаблонной функции. - person Rob Kennedy; 11.12.2009
comment
Иногда вызывающая сторона знает во время компиляции, а вызываемая - нет, для этого подходят шаблоны. Конечно, в общем случае никто не знает X до времени выполнения. - person Qwertie; 20.07.2012
comment
Вы не можете использовать alloca в распределителе STL - выделенная память из alloca будет освобождена, когда фрейм стека будет уничтожен - тогда возвращается метод, который должен выделять память. - person Oliver; 16.10.2012

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

void varTest(int iSz)
{
    char *varArray;
    __asm {
        sub esp, iSz       // Create space on the stack for the variable array here
        mov varArray, esp  // save the end of it to our pointer
    }

    // Use the array called varArray here...  

    __asm {
        add esp, iSz       // Variable array is no longer accessible after this point
    } 
}

Опасностей здесь много, но я объясню несколько: 1. Изменение размера переменной на полпути приведет к уничтожению позиции стека 2. Выход за границы массива приведет к уничтожению других переменных и возможного кода 3. Это не работает в 64-битной системе. build ... для этого нужна другая сборка (но макрос может решить эту проблему). 4. Зависит от компилятора (могут возникнуть проблемы с переходом между компиляторами). Я не пробовал, поэтому действительно не знаю.

person Alan    schedule 15.01.2014
comment
... и если вы хотите самостоятельно это сделать, может быть, использовать класс RAII? - person einpoklum; 11.05.2016
comment
Вы можете просто использовать boost :: container :: static_vector thou. - person Viktor Sehr; 11.05.2016
comment
У этого нет эквивалентов для других компиляторов, которые имеют больше необработанных сборок, чем MSVC. VC, вероятно, поймет, что esp изменился, и настроит доступ к стеку, но, например, в GCC вы просто сломаете его полностью - по крайней мере, если вы используете оптимизацию и -fomit-frame-pointer в частности. - person Ruslan; 22.06.2016