Динамическое выделение переменных
Как статическое, так и автоматическое распределение памяти имеют два общих свойства:
Размер переменной/массива должен быть известен во время компиляции.
Выделение и освобождение памяти происходит автоматически (когда переменная создается/уничтожается).
В большинстве случаев с этим всё ОК. Однако, когда дело доходит до работы с пользовательским вводом, то эти ограничения могут привести к проблемам.
Например, при использовании строки для хранения имени пользователя, мы не знаем наперед насколько длинным оно будет, пока пользователь его не введет. Или нам нужно создать игру с непостоянным количеством монстров (во время игры одни монстры умирают, другие появляются, пытаясь, таким образом, убить игрока).
Если нам нужно объявить размер всех переменных во время компиляции, то самое лучшее, что мы можем сделать — это попытаться угадать их максимальный размер, надеясь, что этого будет достаточно:
char name; // будем надеяться, что пользователь введет имя длиной менее 30 символов!
Monster monster; // 30 монстров максимум
Polygon rendering; // этому 3D-рендерингу лучше состоять из менее чем 40000 полигонов!
1 |
charname30;// будем надеяться, что пользователь введет имя длиной менее 30 символов! Monster monster30;// 30 монстров максимум Polygon rendering40000;// этому 3D-рендерингу лучше состоять из менее чем 40000 полигонов! |
Это плохое решение, по крайней мере, по трем причинам:
Во-первых, теряется память, если переменные фактически не используются или используются, но не все. Например, если мы выделим 30 символов для каждого имени, но имена в среднем будут занимать по 15 символов, то потребление памяти получится в два раза больше, чем нам нужно на самом деле. Или рассмотрим массив : если он использует только 20 000 полигонов, то память для других 20 000 полигонов фактически тратится впустую (т.е. не используется)!
Во-вторых, память для большинства обычных переменных (включая фиксированные массивы) выделяется из специального резервуара памяти — стека. Объем памяти стека в программе, как правило, невелик: в Visual Studio он по умолчанию равен 1МБ. Если вы превысите это значение, то произойдет переполнение стека, и операционная система автоматически завершит выполнение вашей программы.
В Visual Studio это можно проверить, запустив следующий фрагмент кода:
int main()
{
int array; // выделяем 1 миллиард целочисленных значений
}
1 |
intmain() { intarray1000000000;// выделяем 1 миллиард целочисленных значений } |
Лимит в 1МБ памяти может быть проблематичным для многих программ, особенно где используется графика.
В-третьих, и самое главное, это может привести к искусственным ограничениям и/или переполнению массива. Что произойдет, если пользователь попытается прочесть 500 записей с диска, но мы выделили память максимум для 400? Либо мы выведем пользователю ошибку, что максимальное количество записей — 400, либо (в худшем случае) выполнится переполнение массива и затем что-то очень нехорошее.
К счастью, эти проблемы легко устраняются с помощью динамического выделения памяти. Динамическое выделение памяти — это способ запроса памяти из операционной системы запущенными программами по мере необходимости. Эта память не выделяется из ограниченной памяти стека программы, а выделяется из гораздо большего хранилища, управляемого операционной системой — кучи. На современных компьютерах размер кучи может составлять гигабайты памяти.
Для динамического выделения памяти одной переменной используется оператор new:
new int; // динамически выделяем целочисленную переменную и сразу же отбрасываем результат (так как нигде его не сохраняем)
1 | newint;// динамически выделяем целочисленную переменную и сразу же отбрасываем результат (так как нигде его не сохраняем) |
В примере, приведенном выше, мы запрашиваем выделение памяти для целочисленной переменной из операционной системы. Оператор new возвращает указатель, содержащий адрес выделенной памяти.
Для доступа к выделенной памяти создается указатель:
int *ptr = new int; // динамически выделяем целочисленную переменную и присваиваем её адрес ptr, чтобы затем иметь доступ к ней
1 | int*ptr=newint;// динамически выделяем целочисленную переменную и присваиваем её адрес ptr, чтобы затем иметь доступ к ней |
Затем мы можем разыменовать указатель для получения значения:
*ptr = 8; // присваиваем значение 8 только что выделенной памяти
1 | *ptr=8;// присваиваем значение 8 только что выделенной памяти |
Вот один из случаев, когда указатели полезны. Без указателя с адресом на только что выделенную память у нас не было бы способа получить доступ к ней.
5. Задача про время выполнения программ.
Рассмотрим систему, имеющую два центральных процессора, у каждого из которых есть два потока (работающих в режиме гипертрейдинга). Предположим, есть три запущенные програмы: P0, P1 и P2 со временем работы 5, 10 и 20 мс соответственно
Сколько времени займет полное выполнение этих программ? Следует принять во внимание, что все три программы загружают центральный процессор на 100 %, не осуществляют блокировку во время выполнения и не меняют центральный процессор, назначенный для их выполнения
Выполнение программ может занять 20, 25, 30 или 35 мсек в зависимости от того как операционная система назначит их выполнение. Если Р0 и Р1 назначены на одном и том же CPU, а Р2 на другом, то
программы выполнятся за 20 мсек. Если Р0 и Р2 назначены на один CPU, а Р1 на другом, то 25 мсек. Если Р1 и Р2 назначены на одном цп, а Р0 на другом, то 30 мсек. Если все три программы будут назначены на один цп то 35 мсек
3. В чем разница между режимом ядра и пользовательским режимом? Объясните, как сочетание двух отдельных режимов помогает в проектировании операционных систем.
Большинство компьютеров имеют два режима работы: режим ядра и режим пользователя. Операционная система — наиболее фундаментальная часть программного обеспечения, работающая в режиме ядра (этот режим называют еще режимом супервизора). В этом режиме она имеет полный доступ ко всему аппаратному обеспечению и может задействовать любую инструкцию, которую машина в состоянии выполнить. Вся остальная часть программного обеспечения работает в режиме пользователя, в котором доступно лишь подмножество инструкций машины. В частности, программам, работающим в режиме пользователя, запрещено использование инструкций, управляющих машиной или осуществляющих операции ввода-вывода (Input/Output — I/O).
Программные инструменты
Среди профессиональных систем для анализа самая популярная, пожалуй, EnCase Forensic. Она позволяет анализировать большие объёмы данных, задавать поиск по ключевым словам, атрибутам и т. п.
EnCase Forensic создает точную побитовую копию всего диска или части данных, после этого верифицирует собранные улики, генерируя хэш MD5 файла собранных доказательств и снимая CRC-значения данных. Это даёт возможность гарантировать, что данные не были изменены, и в любой момент использовать их в виде доказательств в суде.
EnCase Forensic умеет восстанавливать файлы и разделы, искать удаленную информацию и журналы событий, сигнатуры файлов и значения хэша, анализировать составные файлы (архивы) и находить остатки информации в неразмеченном пространстве жесткого диска в встроенном HEX редакторе.
Другой удобный вариант – дистрибутив Digital Evidence & Forensics Toolkit: DEFT Linuix на платформе Lubuntu. Он позволяет находить и анализировать информацию на жестком диске и других носителях, включает систему поиска информации в кэше браузера, сетевые сканеры и утилиты для выявления руткитов.
Для снятия копий дисков обычно используют дистрибутивы вроде Rip Linux, DEFT Linux, CAINE, Paladin, Helix, Kali. Но обычно в них для полноценной работы нужно уметь работать с консолью. Это не всегда просто, потому что ошибки в командах могут привести к уничтожению улик.
Другой вариант – сборка Windows Forensic Environment (WinFE) с графическим интерфейсом. Она была создана сотрудником Microsoft, компьютерным криминалистом. Сборка основана на WinPE и работает аналогично Linux-дистрибутивам, которые не монтируют разделы в процессе загрузки. В системе есть основные инструменты анализа.
51. Этап компиляции загрузки и выполнения
- Этап компиляции (Compile time). Когда на стадии компиляцииизвестно точное место размещения процесса в памяти, тогда непосредственногенерируются физические адреса. При изменении стартового адреса программынеобходимо перекомпилировать ее код.
- Этап загрузки (Load time). Если информация о размещениипрограммы на стадии компиляции отсутствует, компилятор генерирует перемещаемыйкод. В этом случае окончательное связывание откладывается до моментазагрузки. Если стартовый адрес меняется, нужно всего лишь перезагрузить код сучетом измененной величины.
- Этап выполнения (Execution time). Если процесс может бытьперемещен во время выполнения из одной области памяти в другую,с вязывание откладывается до стадиивыполнения. Здесь желательно наличие специализированного оборудования, напримеррегистров перемещения. Их значение прибавляется к каждому адресу,сгенерированному процессом. Большинство современных ОС осуществляет трансляциюадресов на этапе выполнения,используя для этого специальный аппаратный механизм.
Что такое ИМТ процессора
IMC означает «интегрированный контроллер памяти» или встроенный контроллер памяти. Контроллер памяти может быть отделен или интегрирован в другую микросхему, так что интегрированные — это те, которые, как вы предположите, интегрированы в кристалл процессора. Раньше контроллер памяти находился на материнская плата, но уже довольно давно используется только IMC, поскольку он позволяет процессору управлять памятью более быстрым и прямым образом.
Таким образом, IMC — это цифровая схема, которая контролирует поток данных, которые приходят и уходят между самим процессором и Оперативная память. То, что он интегрирован в процессор, позволяет управлять ОЗУ напрямую и быстрее, чем когда контроллеры памяти были на материнской плате.
Перед AMD K8s (выпущен в 2003 году), процессоры AMD имели контроллер памяти в своем северном мосту, но в следующих поколениях AMD впервые интегрировала его в сам процессор . Intel впервые сделала то же самое для процессоров Nehalem в 2008 году, и после этого оба производителя используют только IMC. Кстати, ARM Процессоры с архитектурой также во всех случаях используют контроллер памяти, встроенный в процессор.
34. Планирование процессов
Исходя из трех основных состояний процесса «готов», «выполнение», «заблокирован». Планировщик должен знать, какой процесс находится в каком состоянии. Все усложняется, если ЦП содержит несколько вычислительных ядер. Поэтому в ОС вводятся различные очереди (списки) для планирования процессов.
Исходя из трех состояний процесса вводятся 3 очереди:
-
Очередь задач: множество всех процессов, которые есть в системе
-
Очередь готовых: множество всех процессов, готовых для выполнения, им можно в любой момент дать квант процессорного времени и они будут выполняться.
-
Очередь ожидающих: множество всех заблокированных процессов.
Кадр стека
Кадр стека вызовов stack frame — кусок стека вызовов, сформированный одним вызовом функции.
В кадре стека размещаются все временные значения, которые необходимо хранить, а также значения локальных переменных. Выделение и освобождение памяти в стеке, происходит простым изменением указателя на текущую вершину стека (sp). С точки зрения операций центрального процессора это очень дешёвая операция, кроме того, освобождение памяти происходит автоматически (например, при возврате из функции). Но когда требуется большие объёмы памяти, то стека может не хватить (особенно при рекурсивных вызовах), в таких случаях приходится пользоваться динамической памятью.
Кадр стека вызовов (параметры могут включаться в кадр стека вызвавшей функции)
Во время отладки отладчик позволяет “проходить” по кадрам стека вызовов, сформированных на данный момент. Таким образом можно понять, в каком порядке вызывались функции, из каких мест в исходном коде, каковы значения локальных переменных в каждом вложенном вызове. Эта информация может быть весьма полезна при поиске ошибок в логике программы.
Размер стека задаётся статически. В случае если во время исполнения программа попробует выйти за его пределы (что возможно при слишком глубокой рекурсии), поведение не определено (ситуация UB). В операционных системах с защитой памяти (в частности, современных версиях Windows и в большинстве Unix-подобных систем) программа будет остановлена с ошибкой. Если это произойдёт во время отладки, то отладчик, скорее всего, сообщит причину: “переполнение стека (вызовов)” (call) stack overflow.
Применяйте знания на практике и передавайте другим
Обмен знаниями полезен не только для других людей, но и для вас в первую очередь. Согласно исследованиям учёных, лучше всего мы запоминаем ту информацию, которой поделились с другими.
Это как в анекдоте о молодом и неопытном учителе: «Объяснял классу новый материал трижды. В конце концов, даже сам всё понял!» Так что учителя так много знают о своих предметах не потому, что они все гении, а потому, что передают знания каждый день – из года в год.
Поэтому, что бы вы ни изучали, старайтесь всегда делиться приобретёнными знаниями с кем-то, кому это может быть интересно. Обсудите какие-то особо интересные или спорные моменты, убедитесь, что ваш собеседник понял, о чём вы говорите.
Это самый важный маркер: если вы можете передать свои знания кому-то другому – значит сами всё понимаете. А если вы всё понимаете, то помните и никогда не забудете.
Лучшие друзья памяти и заклятые враги
Какой из методов запоминания вы бы ни выбрали, как самый эффективный и оптимальный, не следует забывать о том, что существуют определённые обстоятельства, которые помогают запоминать, и такие, которые являются первыми врагами памяти.
Ваш самый верный помощник – это здоровый и полноценный сон. Дело в том, что самый большой объём информации передаётся из кратковременной памяти в долговременную именно во время сна. Таким образом, если хотите, чтобы вся информация, полученная за день, записалась – старайтесь спать не менее 7 часов в сутки. И, естественно, не пользоваться снотворными и не смотреть «ужастики» на ночь. Лучше выпить чашку ромашкового чая с мёдом – это и уснуть поможет, и успокоит.
Также следите за тем, чем питаетесь. Существует ряд продуктов, которые улучшают память. Это, в первую очередь, какао. Так что чашка горячего какао с молоком, шоколадка (в разумных пределах, конечно) или даже ложка какао-порошка – это то, что надо. Так что не забывайте пополнять запасы какао, если хотите иметь хорошую память!
А вот самый заклятый враг памяти – это стресс. Жизнь без стрессов, конечно, невозможна. Но убедитесь в том, что вы хотя бы боретесь с ними. Тут вам помогут спорт, медитация, встречи с друзьями, любимые книги и фильмы.
Учёба и работа – это очень важно, но старайтесь отдыхать при первой малейшей возможности и наслаждаться жизнью!
Что ищут криминалисты
Все доступные файлы
В том числе удалённые и частично перезаписанные. Даже так: особенно удалённые.
Первый и самый простой шаг для этого – общедоступные платные и бесплатные программы для восстановления данных, к примеру, R-Studio, RecoverMyFiles, DiskDigger или Photorec. Они найдут файлы, которые вы удалили. Но можно использовать и более специфичные варианты – к примеру, bstrings и т.д.
Кстати, информация о копиях может остаться и после дефрагментации, перемещения и т. п. Понятно, что просто Command + Option + Delete в macOS (или Shift + Delete в Windows) для окончательного удаления тем более недостаточно.
Следы сохранённых фото
Если вы удалили фото и даже несколько раз перезаписали область диска, в которой оно хранилось, шанс восстановить изображения есть. Например, Windows в каждой папке создаёт специальный скрытый файл Thumbs.db. Здесь сохраняются превью изображений из текущей папки в формате JPEG. И когда вы выбираете режим «Эскизы страниц» для отображения содержимого, картинки подгружаются как раз отсюда.
Конечно, восстановить файл в оригинальном качестве Thumbs.db не поможет. Но понять по превью, что изображено на фото, кто с кем сфотографирован и чем занимается, обычно можно.
Просмотреть содержимое Thumbs.db можно с помощью Thumbnail Cache Viewer. Программа Thumbs.db Viewer 2 дает больше возможностей, но она платная.
Чтобы не попасться в ловушку, нужно отключить кэширование эскизов в файлах Thumbs.db. В Windows 10 это делается так: «Выполнить» – запустить Редактор локальной групповой политики командой gpedit.msc – «Конфигурация пользователя» – «Административные шаблоны» – «Компоненты Windows» – «Проводник» – «Отключить кэширование эскизов в скрытых файлах thumbs.db».
Файл подкачки и своп памяти
Операционная система выполняет массу процедур, чтобы работать быстрее. К примеру, она пишет данные в реестр, временные папки и т. п. За счёт этого временные данные, связанные с файлом, отследить вручную довольно сложно.
В Windows есть файл подкачки pagefile.sys и своп памяти hiberfil.sys, который используется в режиме гибернации. Они лежат в корне диска с системой.
Операционная система не позволяет скопировать эти файлы. Но есть утилиты, которые позволяют получить данные из них. Например, есть утилита Foremost из сборки Kali Linux и аналогов. Она позволяет восстанавливать файлы по заголовкам и внутренней структуре.
Foremost запускается из консоли командой:
Первая директория – что будем восстанавливать, вторая – куда будем писать данные. Утилита создаёт папки под файлы различных типов и раскладывает в них найденное.
Другой вариант – утилита FTK Imager. В меню нужно выбрать File – Add Evidence Item и указать нужный диск, затем экспортировать файл через контекстное меню. Затем скачанный файл можно анализировать с помощью DiskDigger или PhotoRec.
Отметим, что в pagefile.sys и hiberfil.sys есть не только файлы, которые вы открывали с момента последней загрузки Windows. Данные могут лежать несколько недель или даже месяцев.
Отключить файл подкачки можно. В поиске введите «Настройка представления и производительность системы», перейдите к соответствующему разделу панели управления. Затем нажмите «Дополнительно» – «Изменить», снимите галочку «Автоматически выбирать объем файла подкачки», после этого выберите «Без файла подкачки». Система может тормозить, но враги ничего не найдут.
Аппаратная часть
Обычно всё начинается с создания копии диска. Потому что если вдруг в процессе анализа что-то пойдёт не так, можно будет снять ещё одну копию. Или же сразу сделать несколько копий, если над данными работает группа специалистов.
Для создания копий дисков используются системы двух типов:
- блокираторы записи (bridge), через которые носители подключают к компьютеру;
- дубликаторы записи (duplicator), которые умеют автономно создавать полные копии и образы дисков.
Блокираторы перехватывают команды записи от операционной системы и предотвращают их передачу на носитель информации. Они внушают системе, что устройство подключено в режиме «только чтение», а если это не удаётся, то просто сообщают об ошибках записи. Некоторые устройства используют встроенную память для кэширования записанных данных и делают вид, что данные на диске действительно изменились.
Конечно, такие системы недешевы. Например, блокиратор записи T35u обойдётся в 350 долларов, дубликатор Tableau TD2u – в 1600 долларов.
Утечка памяти
Динамически выделенная память не имеет области видимости, т.е. она остается выделенной до тех пор, пока не будет явно освобождена или пока ваша программа не завершит свое выполнение (и операционная система очистит все буфера памяти самостоятельно). Однако указатели, используемые для хранения динамически выделенных адресов памяти, следуют правилам области видимости обычных переменных. Это несоответствие может вызвать интересное поведение, например:
void doSomething()
{
int *ptr = new int;
}
1 |
voiddoSomething() { int*ptr=newint; } |
Здесь мы динамически выделяем целочисленную переменную, но никогда не освобождаем память через использование оператора delete. Поскольку указатели следуют всем тем же правилам, что и обычные переменные, то, когда функция завершит свое выполнение, выйдет из области видимости. Поскольку — это единственная переменная, хранящая адрес динамически выделенной целочисленной переменной, то, когда уничтожится, больше не останется указателей на динамически выделенную память. Это означает, что программа «потеряет» адрес динамически выделенной памяти. И в результате эту динамически выделенную целочисленную переменную нельзя будет удалить.
Это называется утечкой памяти. Утечка памяти происходит, когда ваша программа теряет адрес некоторой динамически выделенной части памяти (например, переменной или массива), прежде чем вернуть её обратно в операционную систему. Когда это происходит, то программа уже не может удалить эту динамически выделенную память, поскольку больше не знает, где выделенная память находится. Операционная система также не может использовать эту память, поскольку считается, что она по-прежнему используется вашей программой.
Утечки памяти «съедают» свободную память во время выполнения программы, уменьшая количество доступной памяти не только для этой программы, но и для других программ также. Программы с серьезными проблемами с утечкой памяти могут «съесть» всю доступную память, в результате чего ваш компьютер будет медленнее работать или даже произойдет сбой. Только после того, как выполнение вашей программы завершится, операционная система сможет очистить и вернуть всю память, которая «утекла».
Хотя утечка памяти может возникнуть и из-за того, что указатель выходит из области видимости, возможны и другие способы, которые могут привести к утечкам памяти. Например, если указателю, хранящему адрес динамически выделенной памяти, присвоить другое значение:
int value = 7;
int *ptr = new int; // выделяем память
ptr = &value; // старый адрес утерян — произойдет утечка памяти
1 |
intvalue=7; int*ptr=newint;// выделяем память ptr=&value;// старый адрес утерян — произойдет утечка памяти |
Это легко решается удалением указателя перед операцией переприсваивания:
int value = 7;
int *ptr = new int; // выделяем память
delete ptr; // возвращаем память обратно в операционную систему
ptr = &value; // переприсваиваем указателю адрес value
1 |
intvalue=7; int*ptr=newint;// выделяем память delete ptr;// возвращаем память обратно в операционную систему ptr=&value;// переприсваиваем указателю адрес value |
Кроме того, утечка памяти также может произойти и через двойное выделение памяти:
int *ptr = new int;
ptr = new int; // старый адрес утерян — произойдет утечка памяти
1 |
int*ptr=newint; ptr=newint;// старый адрес утерян — произойдет утечка памяти |
Адрес, возвращаемый из второго выделения памяти, перезаписывает адрес из первого выделения. Следовательно, первое динамическое выделение становится утечкой памяти!
Точно так же этого можно избежать удалением указателя перед операцией переприсваивания.
Условные рефлексы воспроизведения
Каждую секунду наш мозг достаёт со своего «архива» бесчисленное количество различной информации – под влиянием того, что мы собираемся делать и чувствуем. Мозг, как огромный софит в театре освещает воспоминания так, как будто мы эти события только что пережили. Буквально 15 минут назад.
Например, когда вы, сидя в кафе, вдруг услышали знакомую песню, и это вызвало у вас очень сильные эмоции (например, вспомнили свою первую любовь), то активизируются два участка мозга. Сначала тот, который реагирует на знакомую мелодию, а потом тот, что заставляет вспомнить человека, с которым вы встречались много лет назад. Даже если вы о нём и не вспоминали годами. Так это работает.
А вот в следующий раз, когда вы услышите знакомую мелодию – эти две области в мозге активизируются одновременно. Таким образом, мозг будет заставлять вас переживать моменты из прошлого вновь и вновь. Какая-то песня будет напоминать вам о любимом человеке, какая-то – о родителях, которых вы давно потеряли и скучаете, какая-то – о раннем утре, когда вы собирались в школу много лет назад…
Вот вам и ключ к тому, чтобы лучше запоминать информацию! Как говорят: «условный рефлекс воспроизведения» вам в помощь. Вы легко можете взять его на вооружение и тренировать свой мозг. Ведь наверняка читали об опытах над мышами, которые ассоциировали приём пищи со звучанием колокольчиков?
Итак, если вы готовитесь к экзамену и чувствуете, что от обилия информации в голове образовалась настоящая каша, сделайте следующее.
Позанимайтесь перед сном, а потом возьмите духи, которые непонятно каким образом очутились в доме, и вы ими никогда ранее не пользовались (то есть с ними не связаны абсолютно никакие воспоминания или ассоциации). Распылите их на себя, потом примите душ и отправляйтесь спать.
В следующий раз, когда вам опять придётся запомнить какую-то информацию для учёбы, опять нанесите на себя этот парфюм
Но, что особо важно: никогда не используйте эти духи в других ситуациях, кроме учёбы!. Теперь достаточно будет нанести на себя эти духи или одеколон перед экзаменом – и мозг «вспомнит всё»
Теперь достаточно будет нанести на себя эти духи или одеколон перед экзаменом – и мозг «вспомнит всё».
Кстати, духи не единственный способ. Вы можете использовать жевательную резинку с определённым вкусом во время запоминания информации или растирать между пальцами гальку – это дело вкуса. Главное, чтобы этими мозговыми стимулами вы пользовались лишь для запоминания определённой информации.