(Передумови: у мене є досвід впровадження компіляторів C і C ++.)
Масиви змінної довжини в C99 були в основному помилковим кроком. Щоб підтримати VLAs, 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 ... але ми справді почали покладатися на неї способами, якими цього немає. Наприклад:
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 "Альтернативи для розширення масиву" , документ Bjarne Stroustrup у жовтні 2013 року про VLAs. POV Bjarne сильно відрізняється від моєї; N3810 зосереджується більше на тому, щоб знайти хороший синтаксис C ++ для речей, а також на відмову від використання необроблених масивів у C ++, тоді як я більше зосередився на наслідках метапрограмування та типовій системі. Я не знаю, чи вважає він наслідки системи метапрограмування / типи вирішеними, вирішуваними чи просто нецікавими.
Гарна публікація в блозі, яка вражає багато цих самих моментів, - це "Законне використання масивів змінної довжини" (Chris Wellons, 2019-10-27).