Слайд 2Причины использования опасного кода.
Управляемый код не допускает использование указателей.
Указатели — это переменные, которые хранят адреса других объектов. Указатели
в некотором роде подобны ссылкам в С#. Основное различие между ними заключается в том, что указатель может указывать на что угодно в памяти, а ссылка всегда указывает на объект "своего" типа. Но если указатель может указывать на что угодно, возможно неправильное его использование. Кроме того, работая с указателями, можно легко внести в код ошибку, которую будет трудно отыскать.
Тем не менее указатели существуют, причем для некоторых типов программ (например, системных утилит) -они — необходимы.
Слайд 3Программирование опасного кода
Все операции с указателями должны быть отмечены как
"опасные", поскольку они выполняются вне управляемого контекста.
Объявление и использование указателей
в С# происходит аналогично тому, как это делается в C/C++. Однако: особенность С# — создание управляемого кода.
Его способность поддерживать неуправляемый код позволяет применять программы к задачам специальной категории. Но такое С# программирование уже не подпадает под определение стандартного. И в самом деле, чтобы скомпилировать неуправляемый код, необходимо использовать опцию компилятора /unsafe.
Слайд 4Использование указателей
Указатели — это переменные, которые хранят адреса других
переменных.
Формат объявления переменной-указателя: ТИП* имя_переменной;
int* ip; float* fp;
Здесь
переменную ip можно использовать для
указания на int-значение, а переменную fp — на float-значение.
Однако не существует реального средства, которое могло бы помешать указателю указывать на"бог-знает-что".
Вот потому-то указатели потенциально опасны.
Слайд 5Различие между способами объявления указателей в С# и C/C++
При
объявлении типа указателя в C/C++ оператор " * " не
распространяется на весь список переменных, участвующих в объявлении.
То есть C/C++ при выполнении инструкции int* p, q; объявляется указатель р на int-значение и int-переменная с именем q.
Эта инструкция эквивалентна объявлениям: int* p ; int q;
В C# инструкция эквивалентна объявлениям: int* р; int* q;
Слайд 6Операторы "*" и "&"
Оператор "&" — унарный. Он
возвращает адрес памяти, по которому расположен его операнд.
Пример
int *
i p ;
int num = 10;
ip = #
Оператор работы с указателями (*) служит дополнением к первому (&) он указывает на значение переменной, адресуемой заданным указателем
int val = *ip;
Оператор "*" также можно использовать с левой стороны от оператора присваивания.
*ip = 100;
Слайд 8Пример
using System;
class UnsafeCode {
unsafe public static void Main() {
int count
= 99;
int* p;
р = &count;
Console.WriteLine("count= " +*p);
*р =
10;
Console.WriteLine("Новый count= " + *р);
}
}
Слайд 10Пример использования модификатора fixed
using System;
class Test {
public int num;
public
Test(int i) { num = i ; }
class FixedCode {
unsafe
public static void Main() {
Test Sо = new Test(19);
fixed (int* p = So.num)
{Console.WriteLine("Начальное значение поля So.num равно " + *р);
*р = 10;
Console.WriteLine("Новое значение поля So.num равно " + *р);
}}}
Слайд 11Доступ к членам структур с помощью указателей
Указатель может ссылаться
на объект структурного типа, если он не содержит ссылочных типов.
При доступе к члену структуры посредством указателя необходимо использовать оператор "стрелка" (->)
Слайд 12Пример
struct MyStruct {
public int x;
public int y;
public int sum() {
return x + y; }
}
------------------
MyStruct о = new MyStruct (
) ;
MyStruct* p; // Объявляем указатель.
р = & о;
р->х = 10;
р->у = 20;
Console.WriteLine("Сумма равна " + p->sum());
Слайд 13Арифметические операции над указателями
С указателями можно использовать : ++,
--, + и -.
int* p;
p =2000;
p++; //p =?
p=p+9;
//p =?
Несмотря на то что складывать указатели нельзя, один указатель можно вычесть из другого(если они оба имеют один базовый тип).
Разность покажет количество элементов базового типа, которые разделяют эти два указателя.
Помимо сложения (и вычитания) указателя и (из) целочисленного значения, а также вычитания двух указателей, над указателями никакие другие арифметические операции не выполняются.
Например, с указателями нельзя складывать float- или
double-значения.
Слайд 14Пример
using System;
class PtrArithDemo {
unsafe public static void Main() {
int x;
int
i;
double d;
int* ip = &i;
double* fp = &d;
Console.WriteLine("int double\n");
for(x=0; x
< 10; x++) {
Console.WriteLine((uint) (ip) + " " +(uint) (fp));
fp++;
ip++;}
}}
Слайд 15Сравнение указателей
Для сравнения указателей любого типа можно использовать следующие операторы:
== != =.
Операторы сравнения сравнивают адреса двух операндов, как если бы они
были беззнаковыми целыми числами.
Для того чтобы результат сравнения указателей поддавался интерпретации, сравниваемые указатели должны быть каким-то образом связаны.
То есть, если pi и р2 указывают на переменные, между которыми существует некоторая связь (как, например, между элементами одного и того же массива), то результат сравнения указателей pi и р2 может иметь определенный смысл.
Слайд 16Пример
using System;
class PtrCompDemo {
unsafe public static void Main() {
int[] nums
= new int[11];
int x;
// Находим средний элемент массива,
fixed (int* start
= &nums[0]) {
fixed(int* end = &nums[nums.Length-1]) {
for(x=0; start+x <= end-x; x++) ;
}
}
Console.WriteLine(
"Средний элемент массива имеет номер " + х ) ;
}}
Слайд 17Указатели и массивы
В С# указатели и массивы связаны между собой.
Например, имя массива без индекса образует указатель на начало этого
массива.
using System;
class PtrArray {
unsafe public static void Main() {
int[] nums = new int[10];
fixed(int* p = &nums[0]; p2 = nums;) {
if(p == p2)
Console.WriteLine("Указатели р и р2 равны");
}}}
Слайд 18Индексация указателя
Указатель, который ссылается на массив, можно индексировать так,
как если бы это было имя массива.
Этот синтаксис обеспечивает
альтернативу арифметическим операциям над указателями, поскольку он более удобен в некоторых ситуациях.
(ptr + i) эквивалентно ptr[i]
При индексировании указателя необходимо помнить следующее.
Во-первых, при этом нарушение границ массива никак не контролируется. Следовательно, существует возможность получить доступ к "элементу" за концом массива, если на него ссылается указатель.
Во-вторых, указатель не имеет свойства Length. Поэтому при использовании указателя невозможно узнать длину массива.
Слайд 19Указатели и строки
Несмотря на то что в С# строки
реализованы как объекты, к отдельным их символам можно получить доступ
с помощью указателя.
Для этого достаточно присвоить
char*-указателю адрес начала этой строки, используя fixed-инструкцию:
fixed(char* p = str ) { II . . .
Слайд 20Массивы указателей
Указатели, подобно данным других типов, могут храниться в
массивах.
int * [ ] p t r s =
new int * [3];
Чтобы присвоить адрес int-переменной с именем var третьему элементу этого
массива указателей, запишите следующее:
ptrs[2] = &var;
Чтобы получить значение переменной var, используйте такой синтаксис:
*ptrs[2]
Слайд 21sizeof
sizeof -Для получения размера (в байтах) одного из С#-типов
значений.
sizeof(тип)
Оператор sizeof можно использовать только в контексте опасного (unsafe)
кода. Таким образом, он предназначен в основном для специальных ситуаций, особенно при работе со смешанным (управляемым и неуправляемым) кодом.
NB Начиная с версии 2.0 языка C# при применении оператора sizeof к встроенным типам больше не требуется использовать небезопасный режим.
Слайд 22Boxing\Unboxing
Упаковка-преобразование представляет собой процесс преобразования типа значения в тип object
или любой другой тип интерфейса, реализуемый этим типом значения.
Когда
тип значения упаковывается средой CLR, она создает программу-оболочку значения внутри System.Object и сохраняет ее в управляемой куче.
Операция распаковки-преобразования извлекает тип значения из объекта. Упаковка-преобразование является неявной; распаковка-преобразование является явной.
Концепция упаковки и распаковки лежит в основе единой системы типов C#, в которой значение любого типа можно рассматривать как объект.
Слайд 23Пример
Упаковка
int i = 123;
// The following line boxes i.
object o
= i;
Распаковка
o = 123;
i = (int)o; // unboxing
Слайд 24Упаковка
int i = 123;
object o = i;
Слайд 25Распаковка
int i = 123; // a value type
object
o = i; // boxing
int j = (int)o;
// unboxing
Слайд 26Резюме
По сравнению с простыми операциями присваивания операции упаковки-преобразования и распаковки-преобразования
являются весьма затратными процессами с точки зрения вычислений.
При выполнении
упаковки-преобразования типа значения необходимо создать и разместить новый объект.
Объем вычислений при выполнении операции распаковки-преобразования, хотя и в меньшей степени, но тоже весьма значителен