Все операции с указателями выполняются в небезопасном контексте. Они перечислены в табл. 15.1.
Таблица 15.1. Операции с указателями
Операция Описание
* Разадресация — получение значения, которое находится по адресу, хранящемуся в
указателе
-> Доступ к элементу структуры через указатель
[ ] Доступ к элементу массива через указатель
& Получение адреса переменной
++, - - Увеличение и уменьшение значения указателя на один адресуемый элемент
+, - Сложение с целой величиной и вычитание указателей
==,! =, <>, <=, >= Сравнение адресов, хранящихся в указателях. Выполняется как сравнение
беззнаковых целых величин
stackalloc Выделение памяти в стеке под переменную, на которую ссылается указатель
Рассмотрим примеры применения операций. Если в указатель занесен адрес объекта, получить доступ к этому объекту можно с помощью операций разадресации и доступа к элементу.
Операция разадресации, или разыменования, предназначена для доступа к величине, адрес которой хранится в указателе. Эту операцию можно использовать как для получения, так и для изменения значения величины, например:
|
|
int a = 5; // целая переменная
int* р = &а; // инициализация указателя адресом а
Console.WriteLine(*p); // операция разадресации, результат: 5
Console.WriteLine(++(*p)); // результат: 6
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; // доступ к элементу массива (увеличение на 5)
*z += 5; // доступ с помощью разадресации (увеличение еще на 5)
++z; // инкремент указателя
}
Console.WriteLine(&t[5] - t); // операция вычитания указателей
}
Оператор fixed фиксирует объект, адрес которого заносится в указатель, для того чтобы его не перемещал сборщик мусора и, таким образом, указатель остался корректным. Фиксация происходит на время выполнения блока, который записан после круглых скобок.
В приведенном примере доступ к элементам массива выполняется двумя способами: путем индексации указателя t и путем разадресации указателя z, значение которого инкрементируется при каждом проходе цикла для перехода к следующему элементу массива.
Конструкцию *переменная можно использовать в левой части оператора присваивания, так как она определяет адрес области памяти. Для простоты эту конструкцию можно считать именем переменной, на которую ссылается указатель. С ней допустимы все действия, определенные для величин соответствующего типа.
Арифметические операции с указателями (сложение с целым, вычитание, инкремент и декремент) автоматически учитывают размер типа величин, адресуемых указателями. Эти операции применимы только к указателям одного типа и имеют смысл в основном при работе со структурами данных, элементы которых размещены в памяти последовательно, например, с массивами. Инкремент перемещает указатель к следующему элементу массива, декремент — к предыдущему. Фактически значение указателя изменяется на величину sizeof (тип), где sizeof — операция получения размера величины указанного типа (в байтах). Эта операция применяется только в небезопасном контексте, с ее помощью можно получать размеры не только стандартных, но и пользовательских типов данных. Для структуры результат может быть больше суммы длин составляющих ее полей из-за выравнивания элементов.
|
|
Если указатель на определенный тип увеличивается или уменьшается на константу, его значение изменяется на величину этой константы, умноженную на размер объекта данного типа, например:
short* p;
p++; // значение р увеличивается на 2
long* q;...
q++; // значение q увеличивается на 4
Разность двух указателей — это разность их значений, деленная на размер типа в байтах. Так, результат выполнения последней операции вывода в приведенном примере равен 5. Суммирование двух указателей не допускается. При записи выражений с указателями следует обращать внимание на приоритеты операций. В качестве примера рассмотрим последовательность действий, заданную в операторе
*р++ = 10;
Поскольку инкремент постфиксный, он выполняется после выполнения операции присваивания. Таким образом, сначала по адресу, записанному в указателе р, будет записано значение 10, а затем указатель увеличится на количество байтов, соответствующее его типу. То же самое можно записать подробнее:
*р = 10; р++;
Выражение (*р)++, напротив, инкрементирует значение, на которое ссылается указатель.
В следующем примере каждый байт беззнакового целого числа х выводится на консоль с помощью указателя t:
uint x - 0xAB10234F;
byte* t = (byte*)&x;
for (int i = 0; i < 4; ++i)
Console.Write("{0:X} ". *t++); // результат: 4F 23 10 AB
Как видите, первоначально указатель t был установлен на младший байт переменной х. Листинг 15.1 иллюстрирует доступ к полю класса и элементу структуры:
Листинг 15.1. Доступ к полю класса и элементу структуры с помощью указателей
using System;
namespace ConsoleApplication1
{
class A
{ public int value = 20; }
struct В
{ public int a; }
class Program
{
unsafe static void Main()
{
A n = new A();
fixed (int* pn = &n.value) ++(*pn);
Console.WriteLine("n = " + n.value); // результат: 21
В b;
В* pb = &b;
pb -> a = 100; // результат: 100
Console.WriteLine(b, a);
}
}
}
Операция stackalloc позволяет выделить память в стеке под заданное количество величин заданного типа: stackalloc тип [ количество ]
Количество задается целочисленным выражением. Если памяти недостаточно, генерируется исключение System.StackOverflowException. Выделенная память ничем не инициализируется и автоматически освобождается при завершении блока, содержащего эту операцию. Пример выделения памяти под пять элементов типа int и их заполнения числами от 0 до 4:
int* p - stackalloc int [5];
for (int i = 0; i < 5; ++i)
{
Console.Write(p[i] + " "); // результат: 0 1 2 3 4
}
В листинге 15.2 приведен пример работы с указателями, взятый из спецификации С# Метод IntToString преобразует переданное ему целое значение в строку символов, заполняя ее посимвольно путем доступа через указатель.
Листинг 15.2. Пример работы с указателями: перевод числа в строку
using System;
class Test
{
static string IntToString(int value)
{
int n = value >= 0? value: -value; unsafe
{
char* buffer = stackalloc 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.WriteLine(IntToString(12345));
Console.WriteLine(IntToString(-999));
}
}