Почему массивы переменной длины не являются частью стандарта C++?
Я не использовал c очень много в последние несколько лет. Когда я прочитал этот вопрос сегодня, я столкнулся с некоторым синтаксисом 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
}
the vector версия становится довольно неуклюжей:
void foo(int x, int y, int z) {
vector< vector< vector<int> > > values( /* Really painful expression here. */);
}
срезы, строки и столбцы также потенциально будут распределены по всей памяти.
глядя на обсуждение в comp.std.c++ ясно, что этот вопрос довольно спорный с некоторыми очень тяжеловесными именами с обеих сторон аргумента. Это, конечно, не очевидно, что std::vector всегда лучшее решение.
13 ответов:
недавно была дискуссия об этом стартовала в usenet:Почему нет VLAs в C++0x.
Я согласен с теми людьми, которые, похоже, согласны с тем, что создание потенциального большого массива в стеке, который обычно имеет мало свободного места, не очень хорошо. Аргумент, если вы знаете размер заранее, вы можете использовать статический массив. И если вы не знаете размер заранее, вы будете писать небезопасный код.
C99 VLAs смогло обеспечить малое преимущество возможности создавать небольшие массивы без потери пространства или вызова конструкторов для неиспользуемых элементов, но они внесут довольно большие изменения в систему типов (вам нужно иметь возможность указывать типы в зависимости от значений времени выполнения - это еще не существует в текущем C++, за исключением
newспецификаторы типов операторов, но они обрабатываются специально, чтобы время выполнения не выходило за рамкиnewоператор).можно использовать
std::vector, но это не совсем то же самое, поскольку он использует динамическую память, и заставить его использовать собственный стек-распределитель не совсем просто (выравнивание тоже проблема). Он также не решает ту же проблему, потому что вектор является контейнером с изменяемым размером, тогда как VLAs имеют фиксированный размер. Элемент Динамический Массив C++ предложение предназначено для внедрения решения на основе библиотеки, в качестве альтернативы VLA на основе языка. Однако, насколько я знаю, это не будет частью C++0x.
(фон: у меня есть некоторый опыт реализации компиляторов C и c++.)
массивы переменной длины в C99 были в основном ошибочным шагом. Чтобы поддержать Власа, C99 пришлось пойти на следующие уступки здравому смыслу:
sizeof xуже не всегда является константой времени компиляции; компилятор иногда должен генерировать код для вычисленияsizeof-выражения во время выполнения.разрешение двумерного VLAs (
int A[x][y]) требуется новый синтаксис для объявления функций, которые принимают 2D VLAs в качестве параметров:void foo(int n, int A[][*]).менее важно в мире C++, но чрезвычайно важно для целевой аудитории программистов встроенных систем C, объявление VLA означает chomping an сколь угодно большой часть вашего стека. Это же гарантированный стек-переполнение и сбой. (В любое время вы объявляете
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'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) в runtime. Имея это в виду, действительно ли имеет смысл тратить какие-либо усилия попытка для интеграции C99-style VLAs в C++?как уже указывал каждый другой ответчик, C++ предоставляет множество механизмов распределения кучи (
std::unique_ptr<int[]> A = new int[n];илиstd::vector<int> A(n);будучи очевидными), когда вы действительно хотите передать идею "я понятия не имею сколько оперативной памяти мне может понадобиться."И C++ предоставляет отличную модель обработки исключений для решения неизбежной ситуации, когда объем ОЗУ, который вам нужен, больше, чем объем ОЗУ, который у вас есть. Но, надеюсь,этой ответ дает вам хорошее представление о том, почему C99-style VLAs были не хорошо подходит для C++ - и даже не очень хорошо подходит для C99. ;)
подробнее по этой теме см. N3810 "Альтернативы для расширений массива", Бьярне Статья Страуструпа от октября 2013 года О Власе. POV Бьярне очень отличается от моего; N3810 больше фокусируется на поиске хорошего C++ish синтаксис для вещей и для того, чтобы препятствовать использованию необработанных массивов в C++, тогда как я больше сосредоточился на последствиях для метапрограммирования и типовой системы. Я не знаю, считает ли он метапрограммирование/типовые системные последствия решенными, разрешимыми или просто неинтересными.
вы всегда можете использовать alloca() для выделения памяти в стеке во время выполнения, если вы хотите:
void foo (int n) { int *values = (int *)alloca(sizeof(int) * n); }выделение в стеке означает, что он будет автоматически освобожден, когда стек разматывается.
краткое Примечание: Как упоминалось в справочной странице Mac OS X для alloca(3), "функция alloca() зависит от машины и компилятора; ее использование запрещено.- Просто чтобы ты знал.
есть ситуации, когда выделение памяти в куче-это очень дорого по сравнению с операциями. Примером может служить матричная математика. Если вы работаете с небольшими матрицами, скажем, от 5 до 10 элементов и делаете много арифметики, накладные расходы malloc будут действительно значительными. В то же время создание размера константы времени компиляции кажется очень расточительным и негибким.
Я думаю, что C++ настолько небезопасен сам по себе, что аргумент "попытаться не добавлять больше небезопасных функций" не является очень крепкий. С другой стороны, поскольку C++, возможно, является наиболее эффективным языком программирования во время выполнения, что делает его более полезным: люди, которые пишут критически важные для производительности программы, в значительной степени используют C++, и им нужно как можно больше производительности. Перемещение материала из кучи в стек является одной из таких возможностей. Сокращение количества блоков кучи-это другое. Разрешение VLAs в качестве членов объекта было бы одним из способов достижения этого. Я работаю над таким предложением. Это немного сложно, конечно, но это кажется вполне выполнимым.
в моей собственной работе я понял, что каждый раз, когда я хотел что-то вроде автоматических массивов переменной длины или alloca (), мне было все равно, что память физически расположена в стеке процессора, просто она исходила от какого-то распределителя стека, который не вызывал медленных поездок в общую кучу. Поэтому у меня есть объект per-thread, который владеет некоторой памятью, из которой он может нажимать/pop буферы переменного размера. На некоторых платформах я позволяю этому расти через mmu. Другие платформы имеют фиксированный размер (обычно сопровождается стеком процессора фиксированного размера, а также потому, что нет mmu). Одна платформа, с которой я работаю (портативная игровая консоль), имеет очень маленький стек процессора, потому что он находится в скудной, быстрой памяти.
Я не говорю, что нажатие буферов переменного размера на стек процессора никогда не требуется. Честно говоря, я был удивлен, когда обнаружил, что это не стандарт, так как, безусловно, кажется, что концепция достаточно хорошо вписывается в язык. Для меня, однако, требования "переменный размер" и "должен быть физически расположен на стеке процессора" никогда не придумывали вместе. Речь шла о скорости, поэтому я сделал свой собственный вид "параллельного стека для буферов данных".
кажется, он будет доступен в C++14:
https://en.wikipedia.org/wiki/C%2B%2B14#Runtime-sized_one_dimensional_arrays
Update: он не попал в C++14.
это рассматривалось для включения в C++ / 1x, но был отброшен (это поправка к тому, что я сказал ранее).
Это было бы менее полезно в C++ в любом случае, так как у нас уже есть
std::vectorчтобы заполнить эту роль.
для этого используйте std::vector. Например:
std::vector<int> values; values.resize(n);память будет выделена в куче, но это только небольшой недостаток производительности. Кроме того, целесообразно не выделять большие блоки данных в стеке, так как он довольно ограничен по размеру.
C99 позволяет VLA. И это накладывает некоторые ограничения на то, как объявить VLA. Дополнительные сведения см. В разделе 6.7.5.2 стандарта. C++ запрещает VLA. Но g++ позволяет это.
Если вы знаете значение во время компиляции, вы можете сделать следующее:
template <int X> void foo(void) { int values[X]; }Edit: вы можете создать вектор, который использует распределитель стека (alloca), так как распределитель является параметром шаблона.
такие массивы являются частью C99, но не частью стандартного C++. как говорили другие, вектор всегда является гораздо лучшим решением, поэтому, вероятно, массивы переменных размеров не находятся в стандарте C++ (или в предлагаемом стандарте C++0x).
кстати, для вопросов о том," почему " стандарт C++ таков, как он есть, модератор Usenet newsgroup comp.станд.c++ это место, чтобы пойти.
вам нужно постоянное выражение для объявления массива в C / C++.
для массивов динамического размера необходимо выделить память в куче, а затем управлять временем жизни этой памяти.
void foo(int n) { int* values = new int[n]; //Declare a variable length array [...] delete [] values; }
У меня есть решение, которое сработало для меня. Я не хотел выделять память из-за фрагментации на подпрограмме, которая должна была выполняться много раз. Ответ крайне опасен, поэтому используйте его на свой страх и риск, но он использует преимущества сборки для резервирования места в стеке. В моем примере ниже используется массив символов (очевидно, что другая переменная размера потребует больше памяти).
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 битной сборке... нужна другая сборка для этого (но макрос может решить эту проблему). 4. Специфичный для компилятора (может возникнуть проблема с перемещением между компиляторами). Я не пробовал, так что я действительно не знаю.
Comments