Это действительно зависит от системы, но современные ОС с виртуальной памятью имеют тенденцию загружать свои образы процесса и выделять память примерно так:
+---------+
| 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 разрешил прямой доступ к памяти операционной системы,без защиты, что означало, что программы пользовательского пространства могли, как правило, иметь прямой доступ или перезаписывать все, что им нравилось.
Однако в адресном пространстве процесса программы выглядели одинаково, только они были описаны как сегмент кода,сегмент данных, куча, сегмент стека и т. д., и это было отображено немного по-другому.Но большинство общих областей все еще были там.
После загрузки программы и необходимых общих библиотек в память и распределения частей программы в нужные области ОС начинает выполнять ваш процесс везде, где его основной методat, и ваша программа оттуда берет верх, совершая системные вызовы по мере необходимости.
Разные системы (встроенные, что угодно) могут иметь очень разные архитектуры, такие как системы без стеков, системы с гарвардской архитектурой (с кодом).и данные хранятся в отдельной физической памяти), системы, которые фактически хранят BSS в постоянной памяти (первоначально установленной программистом) и т. д. Но это общая суть.
Вы сказали:
Я также знаю, что компьютерная программа использует два вида памяти: стек и куча, которые также являются частью основной памяти компьютера.
«Стек»и «куча» - это просто абстрактные понятия, а не (обязательно) физически различные «виды» памяти.
стек - это просто структура данных «последний пришел, первый вышел».В архитектуре x86 к нему можно обратиться произвольно, используя смещение от конца, но наиболее распространенными функциями являются PUSH и POP для добавления и удаления элементов из него соответственно.Он обычно используется для локальных переменных функций (так называемое «автоматическое хранение»), аргументов функций, адресов возврата и т. Д. (Подробнее ниже)
A «куча» - это простопсевдоним для фрагмента памяти, который может быть выделен по требованию и адресован случайным образом (это означает, что вы можете получить доступ к любому месту в нем напрямую).Он обычно используется для структур данных, которые вы выделяете во время выполнения (в 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 в конвейер ЦП, затем в регистры ЦП и оттуда выполняются. [Это было неверно. См. исправление Бена Фойгта ниже.]