Это действительно зависит от системы, но современные ОС с виртуальной памятью, как правило, загружают свои образы процессов и выделяют память примерно так:
+---------+
| stack | function-local variables, return addresses, return values, etc.
| | often grows downward, commonly accessed via "push" and "pop" (but can be
| | accessed randomly, as well; disassemble a program to see)
+---------+
| shared | mapped shared libraries (C libraries, math libs, etc.)
| libs |
+---------+
| hole | unused memory allocated between the heap and stack "chunks", spans the
| | difference between your max and min memory, minus the other totals
+---------+
| heap | dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
| bss | Uninitialized global variables; must be in read-write memory area
+---------+
| data | data segment, for globals and static variables that are initialized
| | (can further be split up into read-only and read-write areas, with
| | read-only areas being stored elsewhere in ROM on some systems)
+---------+
| text | program code, this is the actual executable code that is running.
+---------+
Это общее адресное пространство процесса во многих распространенных системах виртуальной памяти. «Дыра» - это размер вашей общей памяти, минус пространство, занимаемое всеми другими областями; это дает большое пространство для роста кучи. Это также «виртуальный», что означает, что он отображается в вашу фактическую память через таблицу перевода и может фактически храниться в любом месте в реальной памяти. Это делается таким образом, чтобы защитить один процесс от доступа к памяти другого процесса и заставить каждый процесс думать, что он работает в полной системе.
Обратите внимание, что в некоторых системах позиции, например, стека и кучи могут быть в другом порядке (см . Ответ Билли О'Нила ниже для более подробной информации о Win32).
Другие системы могут быть очень разными. DOS, например, работал в реальном режиме , и его распределение памяти при запуске программ выглядело совсем иначе:
+-----------+ top of memory
| extended | above the high memory area, and up to your total memory; needed drivers to
| | be able to access it.
+-----------+ 0x110000
| high | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
| upper | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
| | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+
| DOS | DOS permanent area, kept as small as possible, provided routines for display,
| kernel | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained
| vector | the addresses of routines called when interrupts occurred. e.g.
| table | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that
| | location to service the interrupt.
+-----------+ 0x0
Вы можете видеть, что DOS разрешал прямой доступ к памяти операционной системы, без защиты, что означало, что программы пользовательского пространства могли, как правило, иметь прямой доступ или перезаписывать все, что им нравилось.
Однако в адресном пространстве процесса программы выглядели одинаково, только они были описаны как сегмент кода, сегмент данных, куча, сегмент стека и т. Д., И это отображалось немного по-другому. Но большинство общих областей все еще были там.
После загрузки программы и необходимых общих библиотек в память и распределения частей программы по нужным областям ОС начинает выполнять ваш процесс, где находится основной метод, и ваша программа оттуда берет верх, делая системные вызовы по мере необходимости, когда это нужно им.
Различные системы (встроенные, что угодно) могут иметь очень разные архитектуры, такие как системы без стеков, системы Гарвардской архитектуры (с кодом и данными, хранящимися в отдельной физической памяти), системы, которые фактически сохраняют BSS в постоянной памяти (первоначально устанавливаемой программист) и т. д. Но это общая суть.
Ты сказал:
Я также знаю, что компьютерная программа использует два вида памяти: стек и куча, которые также являются частью основной памяти компьютера.
«Стек» и «куча» - это просто абстрактные понятия, а не (обязательно) физически разные «виды» памяти.
Стек является лишь последним в, первой из структуры данных. В архитектуре x86 к нему можно обратиться произвольно, используя смещение от конца, но наиболее распространенными функциями являются PUSH и POP для добавления и удаления элементов из него соответственно. Он обычно используется для локальных переменных функций (так называемое «автоматическое хранение»), аргументов функций, адресов возврата и т. Д. (Подробнее ниже)
«Куча» это просто прозвище куска памяти , который может быть выделен по требованию, и адресована случайным образом (то есть, вы можете получить доступ в любом месте в нем непосредственно). Он обычно используется для структур данных, которые вы выделяете во время выполнения (в C ++, с использованием new
и delete
, и malloc
и друзей в C и т. Д.).
Стек и куча в архитектуре x86 физически находятся в системной памяти (ОЗУ) и отображаются посредством выделения виртуальной памяти в адресное пространство процесса, как описано выше.
В регистрах ( до сих пор на x86), физически будет находиться внутри процессора (в отличие от ОЗУ) и загружаются в процессоре, из области текста (а также могут быть загружены из других мест в памяти или в других местах в зависимости от команд процессора , которые на самом деле выполнены). По сути, это просто очень маленькие, очень быстрые ячейки памяти на кристалле, которые используются для различных целей.
Расположение регистров сильно зависит от архитектуры (на самом деле регистры, набор инструкций и расположение / дизайн памяти - это именно то, что подразумевается под «архитектурой»), и поэтому я не буду останавливаться на этом, но рекомендую взять курс ассемблера, чтобы лучше их понять.
Ваш вопрос:
В какой момент стек используется для выполнения инструкций? Инструкции идут из ОЗУ, в стек, в регистры?
Стек (в системах / языках, которые имеют и используют их) чаще всего используется следующим образом:
int mul( int x, int y ) {
return x * y; // this stores the result of MULtiplying the two variables
// from the stack into the return value address previously
// allocated, then issues a RET, which resets the stack frame
// based on the arg list, and returns to the address set by
// the CALLer.
}
int main() {
int x = 2, y = 3; // these variables are stored on the stack
mul( x, y ); // this pushes y onto the stack, then x, then a return address,
// allocates space on the stack for a return value,
// then issues an assembly CALL instruction.
}
Напишите простую подобную программу, а затем скомпилируйте ее в сборку ( gcc -S foo.c
если у вас есть доступ к GCC) и посмотрите. Сборка довольно проста для подражания. Вы можете видеть, что стек используется для локальных переменных функций, а также для вызова функций, хранения их аргументов и возвращаемых значений. Это также почему, когда вы делаете что-то вроде:
f( g( h( i ) ) );
Все они вызываются по очереди. Он буквально формирует стек вызовов функций и их аргументов, выполняет их, а затем отбрасывает их, когда возвращается (или поднимается); Однако, как упоминалось выше, стек (в x86) фактически находится в пространстве памяти вашего процесса (в виртуальной памяти), и поэтому им можно манипулировать напрямую; это не отдельный шаг во время выполнения (или, по крайней мере, ортогональный процессу).
Кстати, выше приведено соглашение о вызовах C , также используемое в C ++. Другие языки / системы могут помещать аргументы в стек в другом порядке, а некоторые языки / платформы даже не используют стеки и используют это по-разному.
Также обратите внимание, что это не фактические строки выполнения кода на C. Компилятор преобразовал их в инструкции машинного языка в вашем исполняемом файле. Затем они (обычно) копируются из области TEXT в конвейер ЦП, затем в регистры ЦП и оттуда выполняются. [Это было неправильно. См . Исправление Бена Фойгта ниже.]