Страницы

Поиск по вопросам

вторник, 24 декабря 2019 г.

Почему нельзя создать массив произвольной длины в С++ ( и не только )

#cpp


Как новичок, столкнулся с проблемой, что если на момент компиляции кода размер массива
не известен, то конструкция в виде

int n;
cin >> n;
int arr[n]; 


просто не сработает. Но мне очень бы хотелось, чтобы размер массива задавал конечный
пользователь, не используя при этом библиотеку классов STL или реализацию через указатели.
Я знаю, что для этого следовало бы использовать динамический массив или список. Но
меня интересует вопрос: чем обусловлено такое ограничение? Почему компилятор не дает
создать такую конструкцию?
    


Ответы

Ответ 1



Технически создать такой массив можно. В языке С, начиная с версии С99, такое массивы - VLA - прекрасно поддерживаются. Так что никакого "нельзя" тут нет. (Тема полезности и недостатков таких массивов в С - отдельная тема.) В языке С++ же такие массивы просто запрещены. Причина того, почему языке С++ нет массивов run-time размера кроются, скорее всего, в тонкостях процесса типизации и инициализации. О тонкостях типизации я писал здесь. В том же языке С, например, запрещается указывать инициализатор в объявлении VLA, ибо нет хорошего решения по семантике "лишних" инициализаторов (если вдруг какие-то из них окажутся лишними.) В языке С++, где инициализация существенно боле важна и устроена существенно более сложно, такие массивы решили не поддерживать вообще.

Ответ 2



Почему компилятор не дает создать такую конструкцию? Надо различать решения, принимаемые на стадии компиляции, и решения, принимаемые на стадии выполнения. По классике решения по всем типам переменных и размерам массивов принимаются на стадии компиляции. Преимущества такого подхода - программист полностью контролирует все действия компилятора и понимает, сколько времени и места занимает создание того или иного объекта. Конечно, есть языки в которых все определяется на стадии выполнения, в том числе и типы переменных и размеры всех объектов (в том числе и массивов). Недостатки такого подхода - для обеспечения такой функциональности внутри вызывается громадная библиотека, которая пасет все переменные и массивы. При этом программист не контролирует время выполнения объектов и код перестает быть полностью управляемым. Одно из базовых правил языков С/С++ - "в коде нет ничего, что программист не заказывал бы явно". Да и то это правило нарушается - например статические объекты ВСЕГДА инициализируются нулями. Для десктопа это может быть и не так важно, так как старт программы с диска все равно операция не реального времени. А для встроенных систем это может быть критично, например питание на систему уже подано, а процессор вместо того чтобы уже начать управлять защитой атомного реактора в это время старательно записывает нули в статические переменные. с чем связано такое ограничение, а не то, как его обойти / решить Ну так вот, это ограничение связано с желанием иметь полную управляемость кода и точное понимание, где какой вызов библиотеки будет вставлен, а где не будет. И также с желанием отказаться от всех вызовов библиотек, если это нужно для программиста. А обойти это очень просто - используйте оператор new или библиотеку STL. Там есть массивы переменного размера. Или переходите на языки без статической типизации переменных. UPD1: Я тут решил уточнить, в чем проблемы при создании массива переменного размера на стеке. Предположим, есть функция: void fun(int a){ int b; int c; int array[a]; /*какой-то код*/ } Вроде бы все нормально, просто надо передать переменное значение при создании стекового кадра. Но если после массива с переменной длинной есть еще переменные, то доступ к ним уже невозможно получить зная лишь константные смещения в стековом кадре. Необходимо учитывать длину массива array, а это и есть тот самый дополнительный код, который программист не заказывал. Например: void fun(int a){ int b; int c; int array[a]; int d; int e; /*какой-то код*/ } В этом примере для доступа к переменным "d" и "e" нельзя использовать константные смещения в стековом кадре. Нужна специальная функция для расчета смещений, в зависимости от параметра "a". Если же массивов переменной размерности в стеке несколько, то ситуация еще более усложняется. Например: void fun1(int a, int a1){ int b; int c; int array[a]; int d; int e; int array1[a1]; int f; int g; /*какой-то код*/ } В этом примере смещение при доступе к переменным "f" и "g" должно зависеть от параметров "a" и "a1". Поэтому для исключения лишних вычислений при доступе к переменным (то есть для повышения быстродействия и исключения явно не заказанных вызовов библиотек) создание массивов переменной длинны на стеке запрещено. UPD2: Однако, в Си VLA есть (и как это соотносится с вашими объяснениями причин?) Объяснение одно - в далеком 1972 году Ритчи и Томпсон решили не заморачиваться поддержкой VLA. Потом уже другие люди поглядели и сказали - "VLA сделать можно, давайте сделаем. А если кто-то не хочет, чтобы ему подключалось в оверхед вычисление смещения в каждом стековом кадре, тот просто не будет использовать VLA". Это так же, как с оверхедом при использовании виртуальных функций. Кто не хочет, чтобы у него был оверхед при использовании виртуальных функций, тот не использует виртуальные функции. Просто надо понимать, в чем тут оверхед и соответственно принимать решение, что в данный момент важнее (и быстрее) - иметь VLA или доступ к каждой стековой переменной будет в 10 раз дольше. UPD3: ваше утверждение "...доступ к каждой стековой переменной будет в 10 раз дольше..." к какому компилятору относится? Да к любому компилятору это относится. Если стековый кадр переменной длинны, то хочешь-не хочешь а надо вычислять смещения в рантайме. У меня GCC не развернут, так что я проверить не могу. А Вы попробуйте странслировать второй пример, который я привел, с двумя массивами VLA в функции fun1. Там должно быть как минимум два умножения, два сложения и пересылки между регистрами, чтобы косвенно-индексную адресацию реализовать. Причем количество операций линейно растет в зависимости от количества массивов VLA в функции. Если вычисление смещения будет оформлено как подпрограмма, то добавится еще вход-выход в подпрограмму. Надо еще учесть, что сама выборка дополнительных команд занимает время. Так что десятикратное увеличение времени доступа это еще оптимистичная оценка. Напомню, что доступ к стековым переменным в случае константных смещений занимает одну инструкцию и в общем виде (без привязки к конкретной архитектуре) имеет вид (условно): mov ax, k(sp) где "k" это и есть то самое константное смещение переменной в стековом кадре. Количество команд для извлечения элемента что обычного, что VLA-массива одно и то же -- ровно одна movl.) Надо смотреть не количество команд для извлечения элементов VLA массива, а количество команд для доступа к другим переменным в стековом кадре у той функции, у которой определен VLA массив. UPD4: Уже во время выполнения происходит выделение памяти под VLA Так что это, GCC размещает VLA массив в куче? Это вообще не честно, так как весь смысл в том, чтобы кучу не трогать. Операция обращения к куче это долгая операция, а операция выделения стекового кадра это копейки. Вобщем при размещении VLA массива в куче, конечно, никакого оверхеда для доступа к стековым переменным не будет. Зато будет конский оверхед (десятки миллисекунд на современной винде) из-за размещения и удаления массива в куче. И вообще, какой смысл размещать VLA массив в куче, если можно явно вызвать malloc? Вобщем это профанация, а не VLA массивы. Фу, фу, фу я был гораздо лучшего мнения о GCC.

Ответ 3



не используя при этом библиотеку классов STL или реализацию через указатели. Какое то странное ограничение. Писать на C++ и не использовать cout, vector, map, ... давайте ещё от ООП откажемся, и что останется? Собственно этой мой ответ - ООП, не хотите STL - рисуйте свой std::array / std::vector. В чём проблема? Как там, malloc / delete,new / delete,xalloc / delete, ох, не забыть бы delete.... может взять stl и RAII? может это всё извращение? может нервные клетки и правда не восстанавливаюцца? ох, ладна.... нет... продолжим... new / delete,new / delete,new / delete... Ведь язык C++ это язык высокого уровне - не с проста... это говорит о том, что программист может сам нарисовать, используя Объектно-ориентированное программирование, все объекты которые ему необходимы, а затем делать всё что вздумаеться ему с этими объектами. К слову сам STL написан на C++, ограничение - не использовать STL - это всё равно что - не использовать язык C++, нет?

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

Отправить комментарий