
- •Ста: Лекция №1 - Введение. Данные в памяти программ
- •Введение
- •Модель памяти в прикладных программах
- •Intmain ()
- •Int main ()
- •Int main ()
- •Int main ()
- •Char buf[ 2000000 ];
- •Return f();// бесконечно долго вызываем сами себя // вызываем переполнение стека через время
- •Int main ()
- •Return f();
- •Int main ()
- •Delete[]p;// Освобождаем память
- •Int main ()
- •Int a[ n ];// ошибка, размер нельзя вычислить во время компиляции
- •Delete[]p;
- •Int main ()
- •Проблема фиксированного размера массивов
- •Intmain ()
- •Int main ()
- •Int main ()
- •Динамически растущие массивы (векторы)
- •Int main ()
- •Struct IntegerVector
- •Int * m_pData;
- •Int m_nUsed;
- •Int m_nAllocated;
- •Void IntegerVectorDestroy ( IntegerVector & _vector )
- •#Ifndef_integer_vector_hpp_
- •#Include"integer_vector.Hpp"
- •Void IntegerVectorRead ( IntegerVector & _vector, std::istream & _stream );
Модель памяти в прикладных программах
Обрабатываемые данные нужно тем или иным образом хранить в памяти компьютера. Прикладные программы никогда не работают с физической оперативной памятью компьютера непосредственно. Вместо этого операционная система создает для каждой запускаемой программы обособленное адресное пространство виртуальной памяти с косвенной логической адресацией. Программы используют только логические адреса через указатели, при обращении к которым диспетчер памяти конвертирует их в физические. При этом, осуществляются проверки прав доступа, и ни одна программа не имеет доступа к содержимому памяти другой программы или ядра операционной системы. Логически адресуемая память не всегда размещается в физических чипах оперативной памяти, данные могут храниться на диске в файле подкачки (swap), на видеокарте или в другом месте. В ряде случаев (например, одинаковые блоки данных) нескольким логическим адресам могут соответствовать одинаковые физические.
Принципы организации адресов, разделения памяти на сегменты и страницы, логика кэширования - все эти сложные низкоуровневые механизмы скрыты от прикладного программиста в глубине конкретной операционной системы. Обычные прикладные программы работают с относительно простой для понимания моделью виртуальной памяти.
Виртуальная память любой программы логически разделяется на 4 области, каждая из которых имеет совершенно различное предназначение:
сегмент кода (code segment);
сегмент данных (data segment);
сегмент стека (stack segment);
куча (heap).
При запуске программы с диска или другого постоянного накопителя в сегмент кода (code segment) из исполняемых файлов и используемых ими динамически подключаемых библиотечных модулей (DLL - Dynamic Linked Libraries) загружаются машинные инструкции, полученные ранее в процессе сборки программ. Центральный процессор выполняет именно эти инструкции. Также в сегменте кода размещаются константные глобальные данные, например, строковые литералы.
Сегмент кода доступен программам только для чтения. Любая попытка программы осуществить запись по адресам в сегменте кода приведет к фатальному завершению программы. Это легко продемонстрировать на строковых литералах:
Intmain ()
{
char* s = "hello Code Segment!";
s[ 0 ] = 'H';
}
Содержимое строки будет размещено в сегменте кода. Вторая инструкция приведет к фатальному завершению, при этом среда выполнения сообщит о нарушении прав доступа к памяти:
В сегменте данных(data segment), также называемом статической памятью, размещаются переменные, существующие от начала работы до завершения программы. К таким переменным относятся глобальные, а также статические локальные переменные функций (переменные, сохраняющие свое значение между вызовами). Обычно все такие переменные соседствуют в памяти. В случае неаккуратной манипуляции с адресами в данной области можно непроизвольно изменить другую переменную такого же типа:
#include <iostream>
int a = 1, b = 2;
Int main ()
{
int * p = & a; // берем адрес глобальной переменной
*( p + 1 ) = 3; // модифицируем соседнюю ячейку - место хранения b
std::cout << b << std::endl; // выведет 3
}
Сегмент стека(stack segment), также называемый автоматической памятью, содержит локальные переменные и аргументы функций. Также при вызовах функций здесь размещается служебная информация, такая как состояние регистров процессора до вызова для последующего восстановления, адрес инструкции в сегменте кода для возврата после завершения работы вызванной функции, адрес переменной-результата для больших объектов и др. Стек заполняется снизу вверх по мере необходимости, в зависимости от характера вызовов функций и содержащихся в них переменных и связанных служебных данных. При выходе из функции задействованная ей память в стеке считается свободной и может быть использована другими функциями.
Ниже представлено содержимое стека в 3 момента времени, соответствующие состояниям до вызова функции f, внутри его и после возврата в функцию main:
void f ( int x )
{
int y = x + 4; // Точка 2
// ...
}