2013-06-22 4 views
2

Является ли это лишь одним из тех вопросов, которые этот язык «работает»? EDIT:Почему динамическая память позволяет манипулировать массивами во время выполнения?

Почему динамическая память позволяет распределять размеры массивов во время выполнения?

Почему я не могу использовать переменную, вызванную из стека, в отличие от переменной, вызванной из кучи? Это обе переменные, один из которых только что вызывается из другого места и должен быть освобожден и создан вручную. Переменные, созданные в стеке, могут быть изменены во время выполнения?

+2

Динамическое распределение позволяет выбрать размер массива во время выполнения. Размер объектов, выделенных в стек, должен быть известен во время компиляции. – juanchopanza

+0

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

+5

@juanchopanza - не соответствует действительности (по крайней мере, не соответствует действующему стандарту). Вы можете сделать что-то вроде 'int a [n]' где 'n' - параметр, переданный функции –

ответ

4

Почему я не могу использовать переменную, вызванную из стека, в отличие от переменной, вызываемой из кучи?

Расположение кучи дает вам больше контроля над памятью.

Кроме того, существуют ограничения, когда дело доходит до переменных стека.

// предупреждение: применяется только к ПК, возможно, не относится к другим архитектурным сооружениям, с которыми я не знаком. (mips, motorola 68000 и т. д. Все, что не связано с x86, в основном).

Переменные, созданные в стеке, могут быть изменены во время выполнения вправо?

Их РАЗМЕР не может измениться.

  1. Стек имеет ограниченный размер. Размер определяется оператором и компилятором. Если стек становится слишком большим, программа умирает из-за переполнения стека. Классический пример:

    int main(int argc, char** argv){ 
        char buffer[1024*1024*64]; 
        buffer[0] = 0; 
        return 0; 
    } 
    

    Эта программа выйдет из строя, если скомпилирована с настройками по умолчанию в Windows, и она также должна аварийно завершить работу с Linux. Это связано с тем, что размер стека по умолчанию составляет 1 МБ на 32-битных окнах и 8 МБ на 32-битном Linux (система может изменить это, хотя, используя ulimit), и массив размером не более 64 мегабайт не поместится на стек.

  2. Если ваша varaible находится между двумя другими переменными в стеке, вы не можете изменить ее размер, несмотря ни на что. По крайней мере, на x86/64 cpus.

  3. Вы можете теоретически увеличить размер массива стека, если это последняя вещь в стеке. (если я правильно помню, возможно, была нестандартная функция C, называемая alloca, которая могла бы выделять массивы в стеке). Тем не менее, вы по-прежнему будете использовать ограничение размера стека.

Чтобы понять ПОЧЕМУ, существуют такие ограничения, вам нужно отступить от C++ и узнать немного сборки. Попытайтесь найти книгу, которая охватывает сегменты (данные/код/​​стек), объясняет, где хранятся обратные адреса функций, и, предпочтительно, сообщает вам об защищенном режиме. Это должно помочь.

Конечно, есть проблема. Ассемблерные знания помогут только конкретному семейству процессоров. Различные процессоры с компилятором C++ могут использовать разные правила.

--update--

Почему позволяют динамической памяти размер массивов, которые будут выделены во время выполнения?

В зависимости от арки размер стека может быть более или менее фиксированным. У вас есть область памяти, зарезервированная для стека, например, по адресам 0x00100000..0x00200000, и все переменные, найденные в стеке, будут где-то в этой области. Расположение новых переменных определяется (если я правильно помню) «указатель стека», который перемещается в направлении, определяемом процессором. Когда вы добавляете новую переменную в стек, указатель стека перемещается (направление движения, определяемое процессором) по переменной размера, а переменная будет располагаться по адресам между старой и новой ячейкой памяти. Поскольку пространство стека может быть ограничено, а также потому, что переменные смежны (плюс адреса возврата функции также хранятся в стеке), вы не можете внезапно замять массив 2 ГБ в середине его. Основная проблема заключается не в ограниченном размере, а в смежных друг с другом переменных.

Теперь HEAP отличается. Распределение кучи, в теории, может вернуть вам любой адрес из всего адресного пространства, но на практике некоторые адреса будут зарезервированы (на 32-битных окнах вы, например, имеете всего 2,3 ГБ, например, из всего пространства 4 ГБ). Поскольку у вас много свободного места, и поскольку выделенные блоки не должны быть смежными, вы можете свободно выделять большой массив и (теоретически) даже изменять их размер (на практике такие функции, как realloc, вероятно, просто создают новый массив, копируют старое содержимое в новый массив, затем уничтожить старый массив).

Обратите внимание, что дополнительные сведения скрыты.Например, адреса, возвращаемые вам функциями распределения кучи, не являются физическими адресами, а виртуальными адресами, и на самом деле ОС может перемещать вашу программу в физической памяти, а (виртуальные) адреса, используемые в программе, остаются неизменными.

Вот почему я предлагаю прочитать сборку. вам не нужно изучать сборку в глубину, но, имея некоторое общее представление о том, что происходит за кулисами, обязательно поможет.

+2

Кстати, при распределении стека в среде x86 можно предотвратить оптимизацию, в частности «опустить указатель фрейма стека». Если каждая функция заранее знает, сколько данных она будет иметь в стеке, указатель фрейма стека избыточен: положение каждой локальной переменной может быть рассчитано с фиксированными смещениями из указателя стека, поэтому EBP можно использовать в качестве дополнительной общей цели регистр и код стека/код очистки могут быть опущены; это не относится к распределению стека во время выполнения, которое требует EBP-адресации. См. Также http://stackoverflow.com/questions/4343850/variable-length-array-penalty-cost –

+0

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

+0

@MatthieuM: Вот почему я добавил: «Различные ЦП могут использовать разные правила». Что касается языков/компиляторов ... Я не слышал о таких компиляторах C++. Возможно, я просто не знаю о них.Что касается других языков, я не думаю, что это имеет значение здесь. Сообщение помечено как C/C++, и AFAIK C++ обычно переводит на машинный код и использует собственный стек. Если на другом языке используется другой тип стека, это будет не-родной стек, созданный специально для этого языка. Другими словами - совершенно другая история. – SigTerm

3

Основная причина использования кучи заключается не в том, что вы можете использовать ее переменную величину. Основное применение заключается в том, что он позволяет сохранять память после завершения определенной функции.

Стандарт для C99 и более поздних версий (но не C++, хотя я считаю, что некоторые компиляторы (g ++?) Имеют расширения, которые позволяют ему, по крайней мере иногда) допускает «массивы переменной длины» в функциях, но как массивы фиксированного размера, они «исчезают», когда функция заканчивается.

В стандарте для C++ все массивы должны иметь «известную (постоянную) длину во время компиляции». Вы должны использовать кучу для создания чего-то, что не имеет постоянной длины, известной во время компиляции. Это «способ работы на языке».

Сказав это, есть разумные основания для этого. Размер стека очень ограничен, и на самом деле довольно «опасно» выходить из стека, потому что нет ничего, что может сделать программа, - он сработает, и нет безопасного и разумного способа восстановления. Запуск из кучи может быть обработан (возникает исключение C++, но, по крайней мере, программа может отобразить некоторое разумное сообщение об ошибке и, возможно, продолжить каким-то образом, даже если это не удалось с тем, что она пыталась сделать, когда она выполнялась из кучи).

Конечно, «C++ way» - это не писать код, который манипулирует размерами массива вручную, а использовать один из предопределенных типов контейнеров, например std::vector и т. П.

Редактировать Обратите внимание, что после того, как массив выделен из кучи, он остается тем размером, который был при его назначении. Что может быть сделано для изменения его размера, так это выделить еще один кусок памяти для второго массива разного размера, а затем скопировать содержимое «старого» массива в «новый» массив - и пока это сделано таким образом, что код может видеть только «текущее» значение адреса массива [указатель на первый элемент], никто не узнает, что это не тот же массив.

0

Стек имеет ограниченный размер, и все, что на нем исчезает, когда ваша функция возвращается с изменением указателя стека. Память кучи может быть намного больше и будет работать до тех пор, пока приложение будет запущено, или кто-то вызовет функцию для освобождения памяти.

Однако в Windows все еще можно выделить «динамически размерную» память в стеке с помощью alloca. Это будет очищено так же, как любые другие локали, основанные на стеке, будут очищены, поэтому вам не нужно явно освобождать память.

Смотрите здесь: http://msdn.microsoft.com/en-US/library/wb1s57t5(v=vs.80).aspx

+0

"и очищается, когда функция возвращается". Неправильное значение значения указателя стека, но стек не очищается. «alloca», но перераспределить его невозможно, поскольку нет realloca – SigTerm

+0

_alloca выделяет байты размера из стека программ. Выделенное пространство автоматически освобождается, когда вызывается вызывающая функция (а не когда выделение просто выходит из области действия). Поэтому не передавайте значение указателя, возвращаемое _alloca в качестве аргумента для бесплатного – paulm

+0

Так что я на самом деле имел в виду, что locals/alloc функции очищаются, когда функция возвращает – paulm