Справочник от Автор24
Поделись лекцией за скидку на Автор24

Небезопасное программирование в языке C#

  • 👀 477 просмотров
  • 📌 414 загрузок
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «Небезопасное программирование в языке C#» pdf
Лекция 3 Небезопасное программирование в языке C# 3.1 Обозначение идентификаторов в системе Windows Все системные программисты, работающие с функциями системы Windows должны понимать обозначение идентификаторов, принятые в системе (которые называют венгерскими записями). Эти обозначения начал применять один из лучших программистов Microsoft Чарльз Симонии (родом из Венгрии), когда создавалась система Windows. В шутку эти обозначения стали называть венгерскими записями и всем последователям и пользователям (разрабатывающим программы в Windows) приходится знакомиться с особенностями принятых в Windows обозначений. Идея заключается в том, что для имен переменных, типов и т.д. используются префиксы, описывающие их тип и характер содержащейся информации. Некоторые префиксы представлены следующим списком: Префикс Значение а – Массив; в – Логический тип (BOOL); by – Беззнаковый символьный тип (BYTE); c – Символьный тип; cb – Счетчик байтов; cr – Цвет; cx,cy – Короткий тип (SHORT); dw – Беззнаковый длинный тип (DWORD); fn – Функция; h – Логический номер (HANDLE или HINSTANCE); i – Целое; m_ – Переменная класса; n – SHORT или int; np – Ближний указатель; p – Указатель; l – Длинный тип(LONG); lp – Дальний указатель; s – Строка; cz – Строка, заканчивающаяся нуль-символом; w – Беззнаковое целое(WORD); x,y – Короткий тип( координата x или y). Например, запись lpfnWndProc, означает, что lpfnWndProc является дальним указателем на функцию WndProc. В приведенных примерах несколько раз упоминалось понятие указателя. Технология программирования с использованием указателей очень широко использовалась при написании системы Windows. Поэтому нам необходимо изучить понятие указателя и правил работы с ним. Это особенно важно, так как многие формальные параметров API функций системы Windows, которые мы будем использовать, являются указателями. «Типы указателей полезны, в основном, при взаимодействии с APIинтерфейсами языка С, но их можно применять и для обращения к памяти, находящейся за пределами управляемой кучи, или при реализации высокопроизводительных участков программы». [Джозеф Албахари стр.171. C# 3.0 справочник] 3.2 Понятие указателя Указатели – это переменные, которые содержат в качестве своих значений адреса памяти компьютера. Здесь следует напомнить определение переменной. Переменная это область памяти, обозначенная идентификатором (именем переменной), в которой могут храниться изменяемые в процессе работы программы данные. Имя переменной должно содержать информацию об адресе памяти, где находятся данные этой переменной (иначе мы не найдем свои данные в памяти компьютера). Но если физический адрес памяти компьютера, где находятся значения обычной переменной, недоступен пользователю, то переменная указатель специально предназначена для работы с физическими адресами памяти компьютера и в том числе с адресами памяти, где находятся данные обычных переменных. Т. е. если есть переменная А, то можно представить, что есть переменная Аptr, в которой находится адрес памяти с данными переменной А. Соотношение между переменной А и указателем-переменной Аptr можно представить следующим рисунком: Aptr Адрес начала переменной А A Значение переменной А Рисунок 3.1 – Переменная А и ее указатель Аptr. Указатели, подобно любым другим переменным, перед своим использованием в программе должны быть объявлены. Формат объявления указателя имеет следующий вид: тип* переменная; где тип – это тип значения в области памяти, адрес которой будет храниться в переменной. Тип не может быть классом, но может быть структурой, перечислением или указателем, а также любым из значимых типов и void. Тип void означает, что указатель будет хранить адрес переменной неизвестного типа. Примеры объявления указателей: int* aptr; int a; в этом примере мы объявляем две переменные: a – обычная переменная целого типа; aptr –указатель на значение целого типа. float* xptr, yptr; в этом случае переменные xptr и yptr являются указателями на значения вещественного типа. int*[] mas; в примере рассмотрен вариант объявления массива указателей на значения целого типа. int** iptr; в примере объявлен указатель на указатель целого типа. Символ «*» в объявлении переменных показывает, что соответствующая ей переменная является указателем (т.е. предназначена для хранения адреса). Еще раз напомним, что значение указателя – это адрес памяти компьютера. 3.3 Операция инициализации указателя Указатель это переменная, поэтому с ней можно выполнять некоторые операции. Главной операцией любой переменной является операция инициализации (присваивания) – переменной присваивается некоторое значение. Указатель не исключение, но в качестве значения необходимо иметь адрес памяти компьютера. Для получения физического адреса памяти компьютера в языке C# имеется специальная операция – операция адресации «&», которая возвращает адрес памяти переменной или других данных, перед которыми эта операция установлена. Например, Aptr = &a; . В этом примере указателю Aptr будет присвоен адрес переменной a – начальный адрес области памяти компьютера, где хранятся данные переменной a. При этом Aptr должна быть объявлена как указатель переменной того же типа, что и переменная a. Значению указателя можно присвоить значение другого указателя, но только соответствующего типа, например, если Aptr и Bptr указатели на значения целого типа и значение Bptr уже определено (иначе в присваивании нет смысла), то допустима следующая запись: Aptr = Bptr; . Объявление переменных в языке C# должно сопровождаться их инициализацией, поэтому для указателей в язык C# была введена константа фиктивного «нулевого» адреса памяти компьютера – null. Значение указателя любого типа может быть присвоено этому «нулевому» адресу памяти компьютера, как при инициализации, так и в процессе работы программы, например: int* Aptr = null; float Fptr; Fptr = null; В этом примере указателям Aptr и Fptr присвоен фиктивный «нулевой» адреса памяти компьютера – null. В процессе работы программы (или при инициализации указателей) указателям можно присваивать адрес памяти компьютера в явном виде, но только в том случае если этот адрес Вам точно известен, иначе может произойти сбой программы, например: byte* Aptr = (byte *) 0x3F0ECD4; char* Cptr = (char *) 0x3F0ECD4; где, 0x3F0ECD4 – шестнадцатеричная константа адреса памяти, (byte *) или (char *) – операции приведения типов, в которых константа приводится к соответствующему типу указателя. Примеры инициализации массива указателей и выделение памяти под стек с помощью указателей рассмотрим на конкретных примерах позже. 3.4 Другие операции с указателями В языке C# существует операция разыменование, обозначаемая символом «*». Эта операция обратная операции адресации. Если операция адресации по данным получает их адрес, то операция разыменования по адресу получает данные. Естественно в этой операции должен участвовать указатель. Например, a=*Aptr; переменной a присваиваются данные, адрес которых находятся в указателе Aptr. При этом Aptr должна быть объявлена как указатель переменной того же типа, что и переменная a. При работе с данными типа структура в языке C# имеется специальная операция «указатель на элемент» или операция доступа к элементу структуры через указатель, которая обозначается комбинацией символов «->». Например, если PtrCtyd обозначает указатель на некоторую структуру, а Name является полем этой структуры, то запись PtrCtyd->Name эквивалентна (*PtrCtyd).Name. Т.е. по адресу *PtrCtyd мы обращаемся ко всей структуре и в ней выбираем поле Name. Основные операции с указателями представлены в таблице 3.1. Таблица 3.1 Основные операции с указателями Операция Описание * Разыменования – получение значения, которое находится по адресу, хранящемуся в указателе -> Доступ к элементу структуры через указатель [] Доступ к элементу массива через указатель & Получение адреса переменной ++, -Увеличение и уменьшение значения указателя на один адресуемый элемент +, Сложение с целой величиной и вычитание указателей ==, !=, <>, Сравнение адресов, хранящихся в указателях. Выполняется <=, >= Stackalloc как сравнение беззнаковых целых величин Выделение памяти в стеке под переменную, на которую ссылается указатель 3.5 Понятие небезопасного кода Одним из основных достоинств языка C# является его схема работы с памятью: автоматическое выделение памяти под объекты и автоматическая уборка мусора. При этом невозможно обратиться по несуществующему адресу памяти или выйти за границы массива, что делает программы более надежными и безопасными и исключает возможность появления целого класса ошибок. Указатели же позволяют работать напрямую с адресами областей памяти, что входит в противоречие с принципами языка C#. Поэтому программный код, использующий указатели, стали называть небезопасным. В общем случае небезопасным называется код, выполнение которого среда CLR не контролирует. Для разрешения конфликта кода со средой CLR такой код должен быть явным образом помечен с помощью ключевого слова unsafe, которое определяет так называемый небезопасный контекст выполнения. Ключевое слово unsafe может использоваться как спецификатор при объявлении класса, структуры или поля данных структуры, например, public unsafe struct Ctyd {}. В этом случае все поля структуры помечаются как небезопасные. Если ключевое слово unsafe используется как спецификатор для объявления только одного поля структуры, например, public struct Ctyd {… public unsafe int * Kol; … } то условие небезопасности распространяется только на отмеченное поле этой структуры. Ключевое слово unsafe может использоваться как сложный оператор, например: unsafe { блок }, при этом все операторы, входящие в указанный блок будут являться небезопасными – т.е. их работа не будет контролироваться средой CLR и, что особенно важно, динамическая память, выделяемая под небезопасные переменные не будет «зачищаться» сборщиком мусора языка C#. 3.6 Пример программы работы с указателями Для лучшего понимания правил работы с указателями и выполнения операций над ними рассмотрим чисто учебный пример, в котором рассмотрим работу операций адресации и разыменования. Для успешной компиляции кода консольного приложения, содержащего небезопасный фрагмент, необходимо изменить настройки среды Visual Studio следующим образом: выбрать команды Project-> ConsoleAplication1 Properties-> Build и в открывшемся окне установить «птичку» в пункте Allow Unsafe Code. Работу с окном закончить через кнопку Advanced, подтверждая изменения нажатием кнопки OK. В коде приложения ключевое слово unsafe можно использовать как спецификатор метода Main, например, static unsafe void Main(), тогда все операторы этого метода будут рассматриваться как небезопасные. Исходный код программы (отдельные фрагменты кода взяты из книги Павловской): using System; namespace ConsoleApplication1 { class Program { static unsafe void Main() { int a, c; uint adr; a = 10; int* aPtr = null; aPtr = &a; Console.WriteLine("*aPtr = " + *aPtr); Console.WriteLine("a = " + a); Console.Write("aPtr = "); adr = (uint)aPtr; byte* b = (byte*)&adr; b = b + 3; Console.Write("0x"); for (int i = 0; i < 4; i++) { Console.Write("{0:X}", *b); b--; } Console.WriteLine(); Console.Write("&a = "); adr = (uint)&a; b = (byte*)&adr; b = b + 3; Console.Write("0x"); for (int i = 0; i < 4; i++) { Console.Write("{0:X}", *b); b--; } Console.WriteLine(); Console.WriteLine("adr = " + adr); c = *& a; Console.WriteLine("*& a = " + c); //c = &* a; //Console.WriteLine("&* a = " + c); Console.Write("&* aPtr = "); adr = (uint)&* aPtr; b = (byte*)&adr; b = b + 3; Console.Write("0x"); for (int i = 0; i < 4; i++) { Console.Write("{0:X}", *b); b--; } Console.WriteLine(); Console.Write("*& aPtr = "); adr = (uint)*& aPtr; b = (byte*)&adr; b = b + 3; Console.Write("0x"); for (int i = 0; i < 4; i++) { Console.Write("{0:X}", *b); b--; } Console.WriteLine(); Console.ReadKey(); } } } Работа программы: *aPtr = 10 a = 10 aPtr = 0x3F0ECD4 &a = 0x3F0ECD4 adr = 66120916 *& a = 10 &* aPtr = 0x3F0ECD4 *& aPtr = 0x3F0ECD4 Две подряд операции разыменования и адресации взаимно исключают друг друга, как для указателя, так и для переменной целого типа (смотри *& a = 10). Две подряд операции адресации и разыменования взаимно исключают друг друга только для указателя. Попытка использовать эту комбинацию для переменной целого типа приводит к ошибке компиляции программы со следующим сообщением: Error 1 The * or -> operator must be applied to a pointer. При работе программы с указателями, хорошим стилем программирования считается, если указатели в начале программы инициализируются, т.е. либо им присваивается некоторые значения, либо указателям присваиваются нулевые значения равные null. 3.7 Использование указателя для типа void* При объявлении указателя можно использовать тип void*, что означает объявление указателя с отложенным типом. Для указателя такого типа void* нельзя применять арифметические операции и операцию разыменования. Однако он может быть использован для неявного преобразования указателя типа void* в указатель любого типа. В тоже время указателю типа void*можно присвоить адрес переменной любого типа. Таким образом, указатель типа void* можно использовать как промежуточное хранилище адресов переменных любого типа. Рассмотрим чисто учебный пример, в котором указателю типа void* поочередно будем присваивать адреса переменных целого и вещественного типов, а затем передавать эти адреса указателям, соответственно для целого и вещественного типа. Исходный код программы: using System; namespace ConsoleApplication1 { class Program { static unsafe void Main() { int a = 10; double b = 15.67; int* aPtr = null; double* bPtr = null; void* cPtr=null; for (int i = 1; i <= 5; i++) { if (i % 2 == 0) { cPtr = &a; aPtr = (int*)cPtr; Console.WriteLine("i = {0} По адресу указателя находится число {1} ", i, *aPtr); } else { cPtr = &b; bPtr = (double*)cPtr; Console.WriteLine("i = {0} По адресу указателя находится число {1} ", i, *bPtr); } } Console.ReadKey(); } } } Работа программы: i = 1 По адресу указателя находится число 15,67 i = 2 По адресу указателя находится число 10 i = 3 По адресу указателя находится число 15,67 i = 4 По адресу указателя находится число 10 i = 5 По адресу указателя находится число 15,67
«Небезопасное программирование в языке C#» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ
Получи помощь с рефератом от ИИ-шки
ИИ ответит за 2 минуты

Тебе могут подойти лекции

Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot