Операции с указателями

Все операции с указателями выполняются в небезопасном контексте. Они пере­числены в табл. 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));

}

}


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: