Невозможно отучить людей изучать самые ненужные предметы.
Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3
Надо знать обо всем понемножку, но все о немногом.
Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы
Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)
Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода
Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5
Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости
Справочник от А до Я
HTML, CSS, JavaScript
Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы
Помогли мы вам |
Как мы видели на примере с использованием Boehm GC в C, сборка мусора сама по себе решает только проблему с утечками памяти. Безопасность памяти обеспечивают уже свойства самого языка.
Ассоциация между сборкой мусора и безопасностью памяти возникает от того, что многие популярные прикладные языки не разрешают ручное управление памятью и адресную арифметику вовсе, — в этих условиях у пользователя просто нет возможности выполнить небезопасную операцию. Однако и возможности освободить память тоже нет, поэтому нужен какой-то механизм автоматического управления, и сборка мусора — самый популярный.
Самый популярный не значит единственный и универсальный. Первая и главная проблема языков с принудительной сборкой мусора — на них можно писать только программы, которые выполняются в пространстве пользователя. Ядру операционной системы или прошивке микроконтроллера не на кого положиться, они вынуждены управлять памятью самостоятельно, а значит, и язык должен поддерживать указатели и адресную арифметику.
Вторая проблема — потеря производительности и предсказуемости времени выполнения. Классические однопоточные сборщики мусора создают паузы в выполнении программы, которые могут быть заметны пользователю. При использовании многопоточных алгоритмов и верной настройке таймеров под задачи конкретного приложения можно свести паузы к минимуму, но свести затраты ресурсов на сборку мусора к нулю невозможно.
Вполне логично, что разработчики языков ищут альтернативные и промежуточные варианты. Давай посмотрим, какими способами разные языки пытаются обеспечить безопасность памяти.
Проблемы с безопасностью памяти в C возникают в первую очередь из-за отсутствия строгой типизации. Функция malloc(
возвращает нетипизированный указатель (void*
), который пользователь может привести к любому типу. Следить за совпадением размера блока памяти с размером данных тоже обязанность пользователя. К примеру, портирование старого кода на 64-битные платформы может принести много веселых минут, если его авторы жестко прописали размер указателя 32 бит.
Ну и самая классическая ошибка, конечно, — случайное обращение к нулевому указателю.
#include <stdio.h>
void main(void) {
char* str = NULL;
printf("%sn", str);
}
$ gcc -o segfault ./segfault.c
$ ./segfault
Segmentation fault (core dumped)
Более современные языки для системного программирования относятся к этому вопросу более ответственно.
Например, в аде нетипизированные указатели — особый и редкий случай. Обычные указатели всегда типизированные. Вместо malloc(
применяется оператор new
с явным указанием типа. Простого способа освободить память «вообще» там тоже нет, вместо этого есть обобщенная функция (дженерик) Ada.
, которую перед использованием нужно специализировать под конкретный тип данных.
Таким способом запросить или освободить неверный объем памяти гораздо сложнее. Использование после освобождения, впрочем, также будет обнаружено только во время выполнения программы.
Указатели в аде называются access types. Например, access
— указатель на целое число.
Для демонстрации сохраним следующий код в файл access_example.
(имя файла должно совпадать с названием основной процедуры).
with Ada.Unchecked_Deallocation;
procedure Access_Example is
type Int_Ptr is access Integer;
-- Специализация дженерика под Int_Ptr
procedure Free_Integer is new Ada.Unchecked_Deallocation
(Object => Integer, Name => Int_Ptr);
P : Int_Ptr;
I : Integer;
begin
-- Запрашиваем память под целое число с помощью new
-- и сохраняем туда значение 42
P := new Integer'(42);
-- Освобождаем память, теперь P = null
Free_Integer(P);
-- Пробуем получить значение по указателю
I := P.all;
end Access_Example;
Теперь скомпилируем с помощью GNAT и запустим.
$ gnatmake ./access_example.adb
gcc -c -I./ -I- ./access_example.adb
gnatbind -x access_example.ali
gnatlink access_example.ali
$ ./access_example
raised CONSTRAINT_ERROR : access_example.adb:17 access check failed
Как видим, тип указателя access
не защитил нас от обращения к освобожденной памяти. Одно хорошо: хотя бы исключение, а не segmentation fault, как в C, так что наша проблема просто баг, а не потенциальная уязвимость.
Однако начиная с Ada 2005 поддерживается и проверка, что указатель ненулевой. Для этого нужно исправить type
на type
. В этом случае наша программа перестанет компилироваться.
$ gnatmake ./access_example.adb
gcc -c -I./ -I- ./access_example.adb
access_example.adb:8:35: non null exclusion of actual and formal "Name" do not match
access_example.adb:10:04: warning: (Ada 2005) null-excluding objects must be initialized
access_example.adb:10:04: warning: "Constraint_Error" will be raised at run time
access_example.adb:15:04: warning: freeing "not null" object will raise Constraint_Error
gnatmake: "./access_example.adb" compilation error
На практике от такого типа мало пользы, поскольку его память невозможно освободить. По этой причине опцию not
обычно применяют для подтипов, чтобы предотвратить использование null
в качестве аргумента функции, а для самих значений применяют обычный указатель.
type Int_Ptr is access Integer;
subtype Initialized_Int_Ptr is not null Int_Ptr;
procedure Some_Proc(Arg: Initialized_Int_Ptr);
|
|