Я не очень много использовал 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
всегда лучшее решение.
источник
Ответы:
Недавно в usenet началось обсуждение этого вопроса: почему в C ++ 0x нет VLA .
Я согласен с теми людьми, которые, похоже, согласны с тем, что создание потенциально большого массива в стеке, в котором обычно мало доступного пространства, не очень хорошо. Аргумент: если вы заранее знаете размер, вы можете использовать статический массив. И если вы не знаете размер заранее, вы напишите небезопасный код.
VLA C99 могли бы обеспечить небольшое преимущество, заключающееся в возможности создания небольших массивов без потери пространства или вызова конструкторов для неиспользуемых элементов, но они будут вносить довольно большие изменения в систему типов (вам нужно иметь возможность указывать типы в зависимости от значений времени выполнения - это еще не существует в текущем C ++, за исключением
new
спецификаторов типов операторов, но они обрабатываются специально, так что время выполнения не выходит за рамки действияnew
оператора).Вы можете использовать
std::vector
, но это не совсем то же самое, так как он использует динамическую память, и заставить его использовать собственный распределитель стека не совсем легко (выравнивание также является проблемой). Это также не решает ту же проблему, потому что вектор является контейнером с изменяемым размером, тогда как VLA имеют фиксированный размер. Предложение C ++ Dynamic Array предназначено для представления решения на основе библиотеки, как альтернативы VLA на основе языка. Однако, насколько я знаю, это не будет частью C ++ 0x.источник
T(*)[]
aT(*)[N]
- в C ++, это недопустимо, поскольку C ++ не знает о «совместимости типов» - она требует точных совпадений), параметров типов, исключений, «против», «деструкторов» и прочего. Я не уверен, окупятся ли преимущества VLA от всей этой работы. Но тогда я никогда не использовал VLA в реальной жизни, поэтому я, вероятно, не знаю хороших вариантов их использования.vector
но требует фиксированного шаблона использования LIFO и поддерживает один или несколько статически распределенных буферов для потока, которые обычно имеют размер в соответствии с наибольшим общим выделением потока когда-либо использовался, но который мог быть явно урезан. Обычное «распределение» в общем случае потребовало бы не что иное, как копирование указателя, вычитание указателя из указателя, сравнение целых чисел и добавление указателя; для де-выделения просто потребовалась бы копия указателя. Не намного медленнее, чем VLA.(Справочная информация: у меня есть некоторый опыт реализации компиляторов C и C ++.)
Массивы переменной длины в C99 были в основном ошибкой. Чтобы поддержать VLA, C99 должен был пойти на следующие уступки здравому смыслу:
sizeof x
больше не всегда константа времени компиляции; иногда компилятор должен генерировать код для оценкиsizeof
-экспрессии во время выполнения.Разрешение двухмерного Власа (
int A[x][y]
) требуется новый синтаксис для объявления функций , которые используют 2D Влас в качестве параметров: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. Например:
Если бы
n
не была константа времени компиляции (т.A
Е. Если бы она была изменяемого типа), то что это за типS
? Будет лиS
тип также определяться только во время выполнения?Как насчет этого:
Компилятор должен сгенерировать код для некоторой реализации
myfunc
. Как должен выглядеть этот код? Как мы можем статически генерировать этот код, если мы не знаем типA1
во время компиляции?Хуже того, что если во время выполнения окажется, что
n1 != n2
, так что!std::is_same<decltype(A1), decltype(A2)>()
? В этом случае вызовmyfunc
даже не должен компилироваться , потому что вывод типа шаблона должен завершиться неудачей! Как мы можем подражать этому поведению во время выполнения?По сути, C ++ движется в направлении внедрения все большего количества решений во время компиляции : генерация кода шаблона,
constexpr
оценка функции и так далее. Тем временем C99 был занят внедрением традиционных решений времени компиляции (напримерsizeof
) во время выполнения . Имея это в виду, действительно ли он даже имеет смысл затрачивать никаких усилий , пытаясь интегрировать Власа C99 стиле в C ++?Как уже отмечали все остальные авторы, C ++ предоставляет множество механизмов выделения кучи (
std::unique_ptr<int[]> A = new int[n];
илиstd::vector<int> A(n);
являющихся очевидными), когда вы действительно хотите донести идею: «Я понятия не имею, сколько ОЗУ мне может понадобиться». А C ++ предоставляет изящную модель обработки исключений для решения неизбежной ситуации, когда объем необходимой вам оперативной памяти больше, чем у вас. Но, надеюсь, этот ответ дает вам хорошее представление о том, почему VLA в стиле C99 не очень подходят для C ++ - и даже не совсем подходят для C99. ;)Для получения дополнительной информации по этой теме см. N3810 «Альтернативы для расширений массивов» , документ Bjarne Stroustrup за октябрь 2013 года о VLA. POV Бьярне очень отличается от моего; N3810 больше фокусируется на поиске хорошего синтаксиса C ++ для вещей и на препятствовании использованию сырых массивов в C ++, тогда как я больше сосредоточился на последствиях для метапрограммирования и системы типов. Я не знаю, считает ли он последствия метапрограммирования / системы типов решенными, разрешимыми или просто неинтересными.
Хорошая запись в блоге, которая затрагивает многие из этих пунктов, - «Законное использование массивов переменной длины» (Chris Wellons, 2019-10-27).
источник
alloca()
должен был быть стандартизирован в C99. VLA - это то, что происходит, когда комитет по стандартам выпрыгивает впереди реализации, а не наоборот.*
Необязательно, вы можете (и должны) написатьint A[][n]
; (3) Вы можете использовать систему типов без фактического объявления каких-либо VLA. Например, функция может принимать массив изменяемого типа, и ее можно вызывать с двумерными массивами без VLA разных размеров. Однако вы делаете правильные очки в последней части вашего сообщения.n
в вашем тестовом примере, и каков был размер вашего стека? Я предлагаю вам попробовать ввести значение, поn
крайней мере, такое же большое, как размер вашего стека. (И если у пользователя нет возможности контролировать значениеn
в вашей программе, тогда я предлагаю вам просто передать максимальное значениеn
прямо в объявление: объявлятьint A[1000]
или что-то еще, что вам нужно. VLA только необходимы и только опасны, когда максимальное значениеn
не ограничено какой-либо маленькой константой времени компиляции.)Вы всегда можете использовать alloca () для выделения памяти в стеке во время выполнения, если хотите:
Распределение по стеку подразумевает, что он будет автоматически освобожден при размотке стека.
Краткое примечание. Как упоминалось на справочной странице Mac OS X для alloca (3), «функция alloca () зависит от машины и компилятора; ее использование не рекомендуется». Просто чтобы ты знал.
источник
if (!p) { p = alloca(strlen(foo)+1); strcpy(p, foo); }
это не может быть сделано с VLA, именно из-за их области применения.C
похоже на решение, а не на самом делеC++
.В своей собственной работе я понял, что каждый раз, когда я хотел что-то вроде автоматического массива переменной длины или alloca (), мне было все равно, что память физически расположена в стеке процессора, просто из некоторый распределитель стека, который не подвергался медленным переходам в общую кучу. Так что у меня есть объект для каждого потока, которому принадлежит некоторая память, из которой он может выдвигать / выталкивать буферы переменного размера. На некоторых платформах я позволяю этому расти через mmu. Другие платформы имеют фиксированный размер (обычно сопровождаемый стеком процессоров фиксированного размера, потому что нет MMU). Одна платформа, с которой я работаю (портативная игровая консоль), в любом случае имеет очень маленький стек процессоров, потому что она находится в дефицитной, быстрой памяти.
Я не говорю, что вставка буферов переменного размера в стек процессора никогда не нужна. Честно говоря, я был удивлён, когда обнаружил, что это не стандартно, так как кажется, что концепция достаточно хорошо вписывается в язык. Для меня, однако, требования «переменный размер» и «должны быть физически расположены в стеке процессора» никогда не встречались вместе. Речь шла о скорости, поэтому я создал свой собственный «параллельный стек для буферов данных».
источник
Существуют ситуации, когда выделение динамической памяти очень дорого по сравнению с выполняемыми операциями. Примером является матричная математика. Если вы работаете с небольшими матрицами, скажем, от 5 до 10 элементов и выполняете много арифметических операций, служебные данные malloc будут действительно значительными. В то же время, делая размер постоянной времени компиляции, кажется очень расточительным и негибким.
Я думаю, что C ++ настолько небезопасен сам по себе, что аргумент «стараться не добавлять больше небезопасных возможностей» не очень силен. С другой стороны, поскольку C ++ является, пожалуй, наиболее эффективными функциями языка программирования во время выполнения, что делает его еще более полезным: люди, которые пишут программы, критичные к производительности, будут в значительной степени использовать C ++, и им потребуется как можно больше производительности. Перемещение вещей из кучи в стек - одна из таких возможностей. Уменьшение количества блоков кучи - другое. Разрешение VLA в качестве членов объекта будет одним из способов достижения этого. Я работаю над таким предложением. Правда, это немного сложно реализовать, но кажется вполне выполнимым.
источник
Кажется, это будет доступно в C ++ 14:
https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays
Обновление: это не сделало это в C ++ 14.
источник
Это рассматривалось для включения в C ++ / 1x, но было исключено (это исправление к тому, что я сказал ранее).
В любом случае это было бы менее полезно в C ++, так как мы уже должны
std::vector
выполнить эту роль.источник
std::vector
вместо, скажем,alloca()
.Используйте для этого std :: vector. Например:
Память будет выделяться в куче, но это содержит лишь небольшой недостаток производительности. Кроме того, разумно не размещать большие блоки данных в стеке, поскольку он довольно ограничен по размеру.
источник
std::vector<int> values(n);
? Используяresize
после строительства, вы запрещаете неподвижные типы.C99 позволяет VLA. И это накладывает некоторые ограничения на то, как объявлять VLA. Подробнее см. 6.7.5.2 стандарта. C ++ запрещает VLA. Но g ++ это позволяет.
источник
Подобные массивы являются частью C99, но не частью стандартного C ++. как уже говорили другие, вектор всегда является гораздо лучшим решением, поэтому, возможно, поэтому массивы переменного размера не входят в стандарт C ++ (или в предложенный стандарт C ++ 0x).
Кстати, для вопросов о том, «почему» стандарт C ++ таков , модерируемая группа новостей Usenet comp.std.c ++ - это то место, куда можно обратиться
источник
Если вы знаете значение во время компиляции, вы можете сделать следующее:
Редактировать: Вы можете создать вектор, который использует распределитель стека (alloca), так как распределитель является параметром шаблона.
источник
У меня есть решение, которое действительно работает для меня. Я не хотел выделять память из-за фрагментации подпрограммы, которую нужно было запускать много раз. Ответ чрезвычайно опасен, поэтому используйте его на свой страх и риск, но он использует преимущества сборки, чтобы зарезервировать место в стеке. В моем примере ниже используется массив символов (очевидно, для переменной другого размера потребуется больше памяти).
Опасностей здесь много, но я объясню несколько: 1. Изменение размера переменной на полпути убило бы позицию стека 2. Превышение границ массива уничтожило бы другие переменные и возможный код 3. Это не работает в 64-битной среде build ... нужна другая сборка для этого (но макрос может решить эту проблему). 4. Специфично для компилятора (могут возникнуть проблемы при перемещении между компиляторами). Я не пробовал, поэтому я действительно не знаю.
источник
esp
изменилось, и настроит свой доступ к стеку, но, например, в GCC вы просто сломаете его полностью - по крайней мере, если вы используете оптимизацию и,-fomit-frame-pointer
в частности.