Лекция 11 - Небезопасный код.ppt
- Количество слайдов: 23
Небезопасный код
Достоинство C# Схема работы с памятью: автоматическое выделение памяти под объекты и автоматическая уборка мусора. При этом невозможно обратиться к несуществующему адресу памяти или выйти за границы массива, что делает программы более надежными или безопасными и исключает целый класс ошибок. В некоторых случаях возникает необходимость работать с другими адресами, например, при взаимодействии с ОС, написании драйверов или программ, время выполнения которых критично. Такую возможность представляет так называемый небезопасный (unsafe) код.
Небезопасным называется код, выполнение которого среда CLR не контролирует. Он работает напрямую с адресами областей памяти посредством указателей и должен быть явным образом помечен с помощью ключевого слова unsafe, которое определяет небезопасный контекст выполнения. Ключевое слово unsafe может использоваться либо как спецификатор, либо как оператор. В первом случае указывается наряду с другими спецификаторами при описании класса, делегата, структуры и т. д. – везде, где допускаются другие спецификаторы. Это определяет небезопасный контекст для описываемого элемента, например:
public unsafe struct Node { public int Value; public Node* Left; public Node* Right; } Вся структура Node делается как небезопасная, что делает возможным использование в ней указателей Left и Right. Можно применить и другой вариант описания, в котором небезопасными являются только соответствующие поля и структуры: public struct Node { public int Value; public unsafe Node* Left; public unsafe Node* Right; }
Оператор unsafe имеет следующий синтаксис: unsafe блок Все операторы, входящие в блок, выполняются в небезопасном контексте. Компиляция кода, содержащего небезопасные фрагменты, должна производиться с ключом unsafe. В среде VS: Project – Properties – Configuration Properties – Build – Allow Unsafe Cоde
Синтаксис указателей Указатели предназначены для хранения адресов областей памяти. Синтаксис указателя: тип* переменная Тип – это тип величины, на которую указывает переменная, т. е. величины, хранящейся по записанному адресу. Тип не может быть классом, но может быть структурой, перечислением, указателем, а также одним из стандартных типов: sbyte, short, ushort, int, uint, long, ulong, char, float, double, bool и void. Последнее означает, что указатель ссылается на переменную неизвестного типа.
Указатель на тип void применяется в тех случаях. Когда конкретный тип объекта, адрес которого требуется хранить, не определен (например, в одной и той же переменной в разные моменты времени требуется хранить адреса объектов различных типов). Указателю на тип void можно присвоить значение указателя любого типа, а также сравнивать его с любыми указателями, но перед выполнением каких-либо действий с областью памяти, на которую он ссылается, требуется преобразовать его к конкретному типу явным образом. Примеры объявления указателей: int* a; Node* p. Node; void* p; int*[] m; // одномерный массив указателей на int** d; // указатель на int Можно: int* a, b, c;
Указатели являются отдельной категорией типов данных. Они не наследуются от типа object, преобразование между типом object и типами указателей невозможно. В частности, для них не выполняются упаковка и распаковка. Однако допускаются преобразования между разными типами указателей, а также между указателями и целыми. Именно потому, что указатели могут содержать адрес любой области памяти, и, следовательно, изменить ее, операции с ними называются небезопасными. Величины типа указателя могу являться локальными переменными, полями, параметрами и возвращаемыми значениями фунцкии. Эти величины подчиняются общим правилам области действия и времени жизни.
Преобразования указателей Для указателей поддерживаются неявные преобразования из любого типа указателя к типу void*. Любому указателю можно присвоить константу null. Кроме того допускаются явные преобразование: • между указателями любого типа; • между указателями любого типа и целыми типами (со знаком и без знака).
Инициализация указателей 1. Присваивание указателю адреса существующего объекта: - с помощью операции получения адреса: int a = 5; int* p = &a; - с помощью значения другого инициализированного указателя: int* r = p; - с помощью имени массива, который трактуется, как адрес: int[ ] b = new int[ ] {10, 20, 30, 50}; fixed (int* t = b) { … }; fixed (int* t = &b[0]) { … };
2. Присваивание указателю адреса области памяти в явном виде: char* v = (char*) 0 x 12 F 69 E; 0 x 12 F 69 E – шестнадцатеричная константа. Использовать этот способ можно только в том случае, если точно известен адрес. Иначе может возникнуть исключение. 3. Присваивание нулевого значения: int* xx = null; 4. Выделение области памяти в стеке и присваивание ее адреса указателю: int* s = stacalloc int [10]; Операция stacalloc выполняет выделение памяти од 10 величин типа и записывает адрес начала этой области в переменную s, которая может трактоваться, как имя массива.
Операции с указателями Операция Описание * Разадресация – получение значения, которое находится по адресу, хранящемуся в указателе -> Доступ к элементу структуры через указатель [] Доступ к элементу массива через указатель & Получение адреса переменной ++, -- Увеличение или уменьшение значения указателя на один адресуемый элемент +, - Сложение с указателей ==, !=, <, > <=, >= Сравнение адресов, хранящихся в указателя. Выполняется как сравнение беззнаковых целых величин. stacalloc Выделение памяти в стеке под переменную, на которую ссылается указатель целой величиной и вычитание
Примеры применения операций. Если в указатель занесен адрес объекта, получить доступ к этому объекту можно с помощью операций разадресации и доступа к элементу. Операция разадресации, или разыменования, предназначена для доступа к величине, адрес которой хранится в указателе. Эту операцию можно использовать как для получения, так и для изменения значения величины: int a = 5; int* p = &a; Console. Write. Line(*p); Console. Write. Line(++(*p)); int [ ] b = new int [ ] {10, 20, 30, 50}; fixed (int* t = b) {int* z = t; for (int i =0; i < b. Length; ++i) { t[i] += 5; *z +=5; ++z; } Console. Write. Line(&t[5] - t); }
Оператор fixed фиксирует объект, адрес которого заносится в указатель, для того, чтобы его не перемещал сборщик мусора и, таким образом, указатель остался корректным. Фиксация происходит на время выполнения блока. Конструкцию *переменная можно использовать только в левой части оператора присваивания, т. к. она определяет адрес области памяти. Для простоты эту конструкцию можно считать именем переменной, на которую ссылается указатель. С ней допустимы все действия, определенные для величин соответствующего типа. Арифметические операции с указателями автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, элементы которых размещены в памяти последовательно, например, с массивами. Инкремент перемещает указатель к следующему элементу массива, декремент – к предыдущему. Фактически значение указателя изменяется на величину sizeof(тип). Эта операция применяется только в небезопасном контексте, с ее помощью можно получать размеры не только стандартных но и пользовательских типов данных. Для структуры результат может быть больше суммы длин составляющих ее полей из-за выравнивания элементов.
Если указатель на определенный тиа увеличивается или уменьшается, на константу, его значение изменяется на величину этой константы, умноженную на размер объекта данного типа, например: short * p; p++; //+2 long* q; q++ //+4 Разность двух указателей – это разность их значений, деленная на размер типа в байтах. Суммирование двух указателей не допускается. Приоритет операций!! *p++ = 10; или *p = 10; p++, но (*p)++
Пример. Каждый байт беззнакового целого числа x выводится на консоль с помощью указателя t uint = 0 x. AB 10234 F; byte* t = (byte*)&x; for (int i = 0; i < 4; ++i) Console. Write(“{0: X}”, *t++) //результат 4 F 23 10 AB Первоначально указатель t был установлен на младший байт переменной x.
Пример. Доступ к полю класса и элементу структуры с помощью указателей. using System; namespace Console. Application 1 { class A { public int value = 20; } struct B { public int a; } class Program { unsafe static void Main() { A n = new A(); fixed (int* pn = &n. value) ++(*pn); Console. Write. Line(“n= ”+ n. value); B b; B* pb = &b; pb -> a = 100; Console. Write. Line(b. a); } }}
Операция stackalloc позволяет выделить память в стеке под заданное количество величин заданного типа: stackalloc тип [количество] Количество задается целочисленным выражением. Если памяти недостаточно, генерируется исключение System. Stack. Overflow. Exception. Выделенная память ничем не инициализируется и автоматически освобождается при завершении блока, содержащего эту операцию. Пример выделения памяти под 5 элементов типа int и их заполнения числами от 0 до 4: int* p = stackalloc int [5]; for (int i = 0; i < 5; ++i) { p[ i ]= i; Console. Write(p[i] + “ ”) ; }
Пример работы с указателями. Перевод числа в строку. using System; class Test { static string Int. To. String( int value) {int n = value >=0 ? value: -value; unsafe { char* buffer = stacalloc char [16]; char* p = buffer+16; do { *--p = (char) (n % 10 + ’ 0’); n /= 10; } while (n != 0); if (value < 0) *--p = ‘-’; return new string (p, 0, (int) (buffer + 16 - p)); }} static void Main { Console. Write. Line (Int. To. String (12345)); Console. Write. Line (Int. To. String (-999)); }}
Регулярные выражения
Пример. Анализ журнала веб-сервера. using System; System. IO; System. Collection. Generic; System. Text. Regular. Expressions; public class Test { public static void Main() { Stream. Reader f = new Stream. Reader(“access_log”); Stream. Writer = new Stream. Writer(“report. htm”); Regex get = new Regex(“GET”); Regex r = new Regex(“ ”); string s, entry; int value; string [] items = new string[40]; Dictionary<string. int> table = new Dictionary<string. int> ();
while ((s = f. Read. Line()) != null) {items = r. Split (s); if (get. Is. Macth (items[5]) && items [8] == “ 200”) { entry = items[0]; value = int. Parse (items[9]); if (table. Contains. Key (entry)) table[entry] +=value; else table[entry] =value; } } f. Close; w. Write(“<html> <head> <title> Report </title> </head> <body>”+ “<table border = 1> <tr> <td> Computer <td> Bytes <tr> ”); foreach (string item in table. Keys) w. Write(“<tr> <td> {0} <td> {1} <tr>”, item, table[item]); w. Write(“</table> </body> </html> “); W. Close; } }
Файл access_log считывается постоянно, каждая строка разбивается на полня, которые заносятся в массив items. Затем, если загрузка прошла успешно, о чем свидетельствуют значения GET и 200 полей 5 и 8, количество переданных байтов (поле 9) преобразуется в целое и заносится в хеш-таблицу по ключу, которым служит адрес, хранящийся в поле 0. Computer Bytes Ppp-48. pool-113. spbnit. ru 107039 Test 223. sovam. comt 2422 210. 82. 124. 83 20025 81. 24. 130. 7 75439 gate. slovo. ru 113692 212. 13. 108. 164 261199
Лекция 11 - Небезопасный код.ppt