Как автоматическое выделение памяти на самом деле работает в C++?

Я чувствую (a! = 1) & & (+ p < = b) & & (+ p! = b - 1) немного более ясно. Другая опция:

интервал t = b-p; (a! = 1 & & < = t & & a! = t-1)

В основном или 0, t, или находится между 2 и t-2 включительно.

10
задан Tarquila 23 October 2009 в 11:58
поделиться

5 ответов

Нет, без оптимизации ...

int main() 
{      
    int i; 
    int *p; 
}

почти ничего не делает - всего лишь пара инструкций для настройки указателя стека, но

int main() 
{ 
    int *p = new int; 
    delete p; 
}

выделяет блок памяти в куче, а затем освобождает его , это очень много работы (я серьезно здесь - выделение кучи - нетривиальная операция).

18
ответ дан 3 December 2019 в 13:22
поделиться
    int i;
    int *p;

^ Выделение одного целого и одного целочисленного указателя в стеке

int *p = new int;
delete p;

^ Выделение одного целочисленного указателя в стеке и блока размера целого числа в куче

РЕДАКТИРОВАТЬ:

Разница между сегментом стека и сегментом кучи

alt text
(источник: maxi-pedia.com )

void another_function(){
   int var1_in_other_function;   /* Stack- main-y-sr-another_function-var1_in_other_function */
   int var2_in_other_function;/* Stack- main-y-sr-another_function-var1_in_other_function-var2_in_other_function */
}
int main() {                     /* Stack- main */
   int y;                        /* Stack- main-y */
   char str;                     /* Stack- main-y-sr */
   another_function();           /*Stack- main-y-sr-another_function*/
   return 1 ;                    /* Stack- main-y-sr */ //stack will be empty after this statement                        
}

Каждый раз, когда запускается какая-либо программа, она сохраняет все свои переменные в специальной ячейке памяти, называемой Сегмент стека . Например, в случае C / C ++ первая вызываемая функция - main. поэтому он будет помещен в стек первым. Любые переменные внутри main будут помещены в стек по мере выполнения программы. Теперь, поскольку main - это первая вызываемая функция, она будет последней функцией, которая вернет любое значение (или будет извлечена из стека).

Теперь, когда вы динамически выделяете память с помощью new , используется другое специальное место в памяти, вызываемое Сегмент кучи. Даже если фактические данные присутствуют в куче, указатель лежит в стеке.

6
ответ дан 3 December 2019 в 13:22
поделиться

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

В нашем процессоре есть конструкция, называемая стеком. Стек - это логическая конструкция, наложенная на физическую память. Допустим, наша оперативная память существует по адресам от E000 до FFFF. Это означает, что наша запущенная программа может использовать эту память любым удобным нам способом. Представим, что наша операционная система говорит, что от E000 до EFFF - это стек, а от F000 до FFFF - это куча.

Стек поддерживается аппаратными средствами и машинными командами. Нам действительно нечего делать, чтобы поддерживать его. Все, что нам (или нашей ОС) нужно сделать, это убедиться, что мы установили правильный адрес для начала стека. Указатель стека - это физический объект, находящийся в аппаратном обеспечении (процессоре) и управляемый инструкциями процессора. В этом случае наш указатель стека будет установлен в EFFF (при условии, что стек растет НАЗАД, что довольно часто, -). В скомпилированном языке, таком как C, когда вы вызываете функцию, он передает все аргументы, которые вы передали функции в стеке. Каждый аргумент имеет определенный размер. int обычно 16- или 32-битный, char - 8-битный и т.д. Давайте представим, что в нашей системе int и int * 16-битные. Для каждого аргумента указатель стека ЗАКРЕПЛЕН (-) на sizeof (аргумент), и аргумент копируется в стек. Затем любые переменные, которые вы объявили в области видимости, помещаются в стек таким же образом, но их значения не инициализируются.

Давайте еще раз рассмотрим два примера, аналогичные вашим двум.

int hello(int eeep)
{
    int i;
    int *p;
}

Что происходит здесь, в нашем 16- Битовая система следующая: 1) поместите eeep в стек. Это означает, что мы уменьшаем указатель стека до EFFD (потому что sizeof (int) равен 2), а затем фактически копируем eeep по адресу EFFE (текущее значение нашего указателя стека, минус 1, потому что наш указатель стека указывает на первое доступное место после выделения). Иногда есть инструкции, которые могут сделать и то, и другое одним махом (при условии, что вы копируете данные, которые умещаются в регистре. В противном случае вам придется вручную скопировать каждый элемент типа данных в нужное место в стеке - порядок имеет значение! ).

2) создать место для i. Вероятно, это означает просто уменьшение указателя стека до EFFB.

3) Создайте место для p. Это, вероятно, означает просто уменьшение указателя стека до EFF9.

Затем наша программа запускается, запоминая, где находятся наши переменные (eeep начинается с EFFE, i - с EFFC, а p - с EFFA). ) попадает в этот черный ящик, называемый HEAP, и получает адрес свободной памяти. Наша операционная система будет управлять кучей за нас, но мы должны сообщить ей, когда нам нужна память и когда мы закончим с ней.

В этом примере, когда мы вызываем malloc (), ОС вернет блок 2 байта (sizeof (int) в нашей системе равно 2), давая нам начальный адрес этих байтов. Допустим, первый звонок дал нам адрес F000. Затем ОС отслеживает, что адреса F000 и F001 используются в настоящее время. Когда мы вызываем free (p), ОС находит блок памяти, на который указывает p, и отмечает 2 байта как неиспользуемые (потому что sizeof (star p) равен 2). Если вместо этого мы выделим больше памяти, адрес F002, скорее всего, будет возвращен как начальный блок новой памяти. Обратите внимание, что malloc () сам по себе является функцией. Когда p помещается в стек для вызова malloc (), p снова копируется в стек по первому открытому адресу, у которого достаточно места в стеке, чтобы соответствовать размеру p (вероятно, EFFB, потому что на этот раз мы поместили в стек только 2 вещи размера 2, а sizeof (p) равно 2), и указатель стека снова уменьшается до EFF9, а malloc () помещает свои локальные переменные в стек, начиная с этого места. Когда malloc завершает работу, он выталкивает все свои элементы из стека и устанавливает указатель стека таким, каким он был до его вызова. Возвращаемое значение malloc (), пустая звезда, скорее всего, будет помещено в какой-нибудь регистр (обычно это аккумулятор во многих системах) для нашего использования.

В реализации оба примера ДЕЙСТВИТЕЛЬНО не так просты. Когда вы выделяете стековую память для вызова новой функции, вы должны убедиться, что вы сохранили свое состояние (сохраните все регистры), чтобы новая функция не работала. t стереть значения навсегда. Обычно при этом их тоже помещают в стек. Таким же образом вы обычно сохраняете регистр счетчика программы, чтобы вы могли вернуться в нужное место после возврата из подпрограммы. Менеджеры памяти используют собственную память, чтобы «запомнить», какая память была выдана, а какая нет. Виртуальная память и сегментация памяти еще больше усложняют этот процесс, и алгоритмы управления памятью должны постоянно перемещать блоки (и защищать их тоже), чтобы предотвратить фрагментацию памяти (отдельная тема), и это связано с виртуальной памятью. также. Второй пример - действительно большая банка червей по сравнению с первым примером. Кроме того, запуск нескольких процессов значительно усложняет все это, поскольку каждый процесс имеет свой собственный стек, и к куче может получить доступ более чем один процесс (что означает, что он должен защищать себя). Кроме того, каждая архитектура процессора отличается. Некоторые архитектуры ожидают, что вы установите указатель стека на первый свободный адрес в стеке, другие будут ожидать, что вы укажете его на первое несвободное место.

Я надеюсь, что это помогло. пожалуйста, дайте мне знать.

обратите внимание, все вышеперечисленные примеры относятся к вымышленной машине, которая слишком упрощена. На реальном оборудовании это становится немного сложнее.

edit: звездочки не отображаются. Я заменил их словом «звезда»


. Как бы то ни было, если мы будем использовать (в основном) тот же код в примерах, заменив «hello» на «example1» и «example2» соответственно, мы получим следующую сборку вывод для инфа на wndows.

    .file   "test1.c"
    .text
.globl _example1
    .def    _example1;  .scl    2;  .type   32; .endef
_example1:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    leave
    ret
.globl _example2
    .def    _example2;  .scl    2;  .type   32; .endef
_example2:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    movl    $4, (%esp)
    call    _malloc
    movl    %eax, -4(%ebp)
    movl    -4(%ebp), %eax
    movl    %eax, (%esp)
    call    _free
    leave
    ret
    .def    _free;  .scl    3;  .type   32; .endef
    .def    _malloc;    .scl    3;  .type   32; .endef
24
ответ дан 3 December 2019 в 13:22
поделиться

Похоже, вы ничего не знаете о стеке и куче. Ваш первый пример - это просто выделение некоторой памяти в стеке, которая будет удалена, как только выйдет за пределы области видимости. Память в куче, полученная с помощью malloc / new, будет оставаться, пока вы не удалите ее с помощью free / delete.

2
ответ дан 3 December 2019 в 13:22
поделиться

В первой программе все ваши переменные находятся в стеке. Вы не выделяете динамическую память. 'p' просто сидит в стеке, и если вы разыменуете его, вы получите мусор. Во второй программе вы фактически создаете целочисленное значение в куче. 'p' на самом деле указывает на некоторую допустимую память в этом случае. На самом деле вы можете разыменовать p и безопасно установить для него что-нибудь значимое:

*p = 5;

Это действительно во второй программе (до удаления), а не в первой. Надеюсь, это поможет.

2
ответ дан 3 December 2019 в 13:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: