Увеличение и уменьшение указателя

В небезопасном контексте операторы ++ и ‑‑ (§7.6.9 и §7.7.5) могут применяться к переменным типа указателя всех типов, кроме void*. Так, для каждого типа указателя T* неявно определены следующие операторы:

T* operator ++(T* x);

T* operator --(T* x);

Эти операторы дают такой же результат, как x + 1 и x - 1 соответственно (§18.5.6). Иначе говоря, для переменной типа указателя с типом T* оператор ++ добавляет sizeof(T) к адресу, содержащемуся в переменной, а оператор ‑‑ вычитает sizeof(T) из адреса, содержащегося в переменной.

Если при операциях увеличения или уменьшения указателя переполняется область типа указателя, результат зависит от реализации, но исключения не создаются.

Арифметические операции с указателем

В небезопасном контексте операторы + и - (§7.8.4 и §7.8.5) могут применяться к значениям всех типов указателей, кроме void*. Так, для каждого типа указателя T* неявно определены следующие операторы:

T* operator +(T* x, int y);
T* operator +(T* x, uint y);
T* operator +(T* x, long y);
T* operator +(T* x, ulong y);

T* operator +(int x, T* y);
T* operator +(uint x, T* y);
T* operator +(long x, T* y);
T* operator +(ulong x, T* y);

T* operator –(T* x, int y);
T* operator –(T* x, uint y);
T* operator –(T* x, long y);
T* operator –(T* x, ulong y);

long operator –(T* x, T* y);

Если дано выражение P типа указателя T* и выражение N типа int, uint, long или ulong, то выражения P + N и N + P вычисляют значение указателя типа T*, получающееся при добавлении значения N * sizeof(T) к адресу, заданному выражением P. Аналогичным образом, выражение P - N вычисляет значение указателя типа T*, получающееся при вычитании значения N * sizeof(T) из адреса, заданного выражением P.

Если даны два выражения, P и Q, типа указателя T*, то выражение P – Q вычисляет разность адресов, заданных P и Q, а затем делит эту разность на sizeof(T). Тип результата всегда long. В действительности P - Q вычисляется как ((long)(P) - (long)(Q)) / sizeof(T).

Пример:

using System;

class Test
{

static void Main() {
unsafe {
int* values = stackalloc int[20];
int* p = &values[1];
int* q = &values[15];
Console.WriteLine("p - q = {0}", p - q);
Console.WriteLine("q - p = {0}", q - p);
}
}
}

В результате получается:

p - q = -14
q - p = 14

Если при арифметических операциях с указателем переполняется область типа указателя, результат усекается способом, зависящим от реализации, но исключения не создаются.

Сравнение указателей

В небезопасном контексте операторы ==, !=, <, >, <= и => (§7.10) можно применять к значениям всех типов указателей. Операторы сравнения указателей следующие:

bool operator ==(void* x, void* y);

bool operator !=(void* x, void* y);

bool operator <(void* x, void* y);

bool operator >(void* x, void* y);

bool operator <=(void* x, void* y);

bool operator >=(void* x, void* y);

Поскольку существует неявное преобразование любого типа указателей к типу void*, операнды любого типа указателей можно сравнивать с помощью этих операторов. Операторы сравнения сравнивают адреса, заданные двумя операндами, как если бы они были беззнаковыми целыми.

Оператор sizeof

Оператор sizeof возвращает число байт, занимаемых переменной данного типа. Тип, указанный в качестве операнда sizeof, должен быть неуправляемым_типом (§18.2).

sizeof-expression:
sizeof ( unmanaged-type )

Результатом оператора sizeof является значение типа int. Для некоторых предопределенных типов оператор sizeof дает постоянное значение, как указано в приведенной ниже таблице.

Выражение Результат
sizeof(sbyte)
sizeof(byte)
sizeof(short)
sizeof(ushort)
sizeof(int)
sizeof(uint)
sizeof(long)
sizeof(ulong)
sizeof(char)
sizeof(float)
sizeof(double)
sizeof(bool)

Для всех других типов результат оператора sizeof определяется реализацией и классифицируется как значение, а не константа.

Порядок объединения членов в структуру не установлен.

Для целей выравнивания могут быть безымянные заполнения в начале, внутри и в конце структуры. Содержимое разрядов, используемых в качестве заполнения, не определено.

При применении к операнду, имеющему тип структуры, результатом является общее число байт в переменной этого типа, включая заполнения.

Оператор fixed

В небезопасном контексте порождение внедренного_оператора (§8) допускает дополнительную конструкцию, оператор fixed, используемый для "фиксирования" перемещаемой переменной, так что ее адрес остается постоянным на время действия этого оператора.

embedded-statement:
...
fixed-statement

fixed-statement:
fixed ( pointer-type fixed-pointer-declarators ) embedded-statement

fixed-pointer-declarators:
fixed-pointer-declarator
fixed-pointer-declarators , fixed-pointer-declarator

fixed-pointer-declarator:
identifier = fixed-pointer-initializer

fixed-pointer-initializer:
& variable-reference
expression

Каждый декларатор_указателя_fixed объявляет локальную переменную данного типа_указателя и инициализирует эту локальную переменную адресом, вычисленным соответствующим инициализатором_указателя_fixed. Локальная переменная, объявленная в операторе fixed, доступна в любом инициализаторе_указателя_fixed, находящемся справа от объявления этой переменной, и во внедренном_операторе оператора fixed. Локальная переменная, объявленная оператором fixed, считается доступной только для чтения. Возникает ошибка времени компиляции, если внедренный оператор пытается изменить эту локальную переменную (через присваивание или операторами ++ и ‑‑) или передать ее в качестве параметра ref или out.

Инициализатором_указателя_fixed может быть одно из следующего:

· лексема "&", за которой следует ссылка_на_переменную (§5.3.3) на перемещаемую переменную (§18.3) неуправляемого типа T при условии, что тип T* является неявно преобразуемым к типу указателя, заданному в операторе fixed. В этом случае инициализатор вычисляет адрес заданной переменной и эта переменная гарантированно остается по фиксированному адресу на время действия оператора fixed;

· выражение типа_массива с элементами неуправляемого типа T при условии, что тип T* является неявно преобразуемым к типу, заданному в операторе fixed. В этом случае инициализатор вычисляет адрес первого элемента массива, а весь массив гарантированно остается по фиксированному адресу на время действия оператора fixed. Поведение оператора fixed определяется реализацией, если выражение массива дает null или если в массиве нуль элементов;

· выражение типа string при условии, что тип char* является неявно преобразуемым к типу указателя, заданному в операторе fixed. В этом случае инициализатор вычисляет адрес первого символа строки, а вся строка гарантированно остается по фиксированному адресу на время действия оператора fixed. Поведение оператора fixed определяется реализацией, если строковое выражение дает null;

· простое_имя или доступ_к_члену, ссылающиеся на член буфера фиксированного объема перемещаемой переменной, при условии, что тип члена буфера фиксированного объема является неявно преобразуемым к типу указателя, заданному в операторе fixed. В этом случае инициализатор вычисляет указатель на первый элемент буфера фиксированного объема (§18.7.2), а буфер фиксированного объема гарантированно остается по фиксированному адресу на время действия оператора fixed.

Для каждого адреса, вычисленного инициализатором_указателя_fixed, оператор fixed гарантирует, что переменная, находящаяся по этому адресу, не подлежит перемещению или удалению сборщиком мусора на время действия оператора fixed. Например, если адрес, вычисленный инициализатором_указателя_fixed, ссылается на поле объекта или на элемент экземпляра массива, оператор fixed гарантирует, что содержащий экземпляр объекта не будет перемещен или удален в течение срока жизни этого оператора.

На программиста возлагается обязанность обеспечить, чтобы указатели, созданные операторами fixed, не продолжали существовать за пределами выполнения этих операторов. Например, если указатели, созданные операторами fixed, передаются внешним API, программист обязан обеспечить, чтобы в этих API не сохранялась память этих указателей.

Фиксированные объекты могут вызвать фрагментацию кучи (поскольку их нельзя перемещать). По этой причине объекты следует фиксировать только при абсолютной необходимости и на как можно более короткий срок.

Пример:

class Test
{
static int x;
int y;

unsafe static void F(int* p) {
*p = 1;
}

static void Main() {
Test t = new Test();
int[] a = new int[10];
unsafe {
fixed (int* p = &x) F(p);
fixed (int* p = &t.y) F(p);
fixed (int* p = &a[0]) F(p);
fixed (int* p = a) F(p);
}
}
}

демонстрируется несколько способов использования оператора fixed. Первый оператор фиксирует и получает адрес статического поля, второй оператор фиксирует и получает адрес поля экземпляра, а третий оператор фиксирует и получает адрес элемента массива. В каждом случае было бы ошибкой использовать обычный оператор &, так как эти переменные классифицируются как перемещаемые переменные.

Четвертый оператор fixed в приведенном выше примере дает тот же результат, что и третий.

В этом примере оператора fixed используется тип string:

class Test
{
static string name = "xx";

unsafe static void F(char* p) {
for (int i = 0; p[i] != '\0'; ++i)
Console.WriteLine(p[i]);
}

static void Main() {
unsafe {
fixed (char* p = name) F(p);
fixed (char* p = "xx") F(p);
}
}
}

В небезопасном контексте элементы массива в одномерном массиве хранятся в порядке возрастания индекса, начиная с индекса 0 и заканчивая индексом Length – 1. В многомерных массивах элементы хранятся так, что сначала возрастают индексы самого правого измерения, затем соседнего левого и так далее влево. В пределах оператора fixed, получающего указатель p на экземпляр массива a, значения указателя в диапазоне от pдо p + a.Length - 1 представляют адреса элементов массива. Аналогично переменные в диапазоне от p[0] до p[a.Length - 1] представляют фактические элементы массива. При данном способе хранения массивов можно обращаться с массивом любого измерения как если бы он был линейным.

Пример:

using System;

class Test
{
static void Main() {
int[,,] a = new int[2,3,4];
unsafe {
fixed (int* p = a) {
for (int i = 0; i < a.Length; ++i) // treat as linear
p[i] = i;
}
}

for (int i = 0; i < 2; ++i)
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 4; ++k)
Console.Write("[{0},{1},{2}] = {3,2} ", i, j, k, a[i,j,k]);
Console.WriteLine();
}
}
}

В результате получается:

[0,0,0] = 0 [0,0,1] = 1 [0,0,2] = 2 [0,0,3] = 3
[0,1,0] = 4 [0,1,1] = 5 [0,1,2] = 6 [0,1,3] = 7
[0,2,0] = 8 [0,2,1] = 9 [0,2,2] = 10 [0,2,3] = 11
[1,0,0] = 12 [1,0,1] = 13 [1,0,2] = 14 [1,0,3] = 15
[1,1,0] = 16 [1,1,1] = 17 [1,1,2] = 18 [1,1,3] = 19
[1,2,0] = 20 [1,2,1] = 21 [1,2,2] = 22 [1,2,3] = 23

В этом примере

class Test
{
unsafe static void Fill(int* p, int count, int value) {
for (; count != 0; count--) *p++ = value;
}

static void Main() {
int[] a = new int[100];
unsafe {
fixed (int* p = a) Fill(p, 100, -1);
}
}
}

оператор fixed используется для фиксирования массива, чтобы его адрес передать методу, принимающему указатель.

В примере:

unsafe struct Font
{
public int size;
public fixed char name[32];
}

class Test
{
unsafe static void PutString(string s, char* buffer, int bufSize) {
int len = s.Length;
if (len > bufSize) len = bufSize;
for (int i = 0; i < len; i++) buffer[i] = s[i];
for (int i = len; i < bufSize; i++) buffer[i] = (char)0;
}

Font f;

unsafe static void Main()
{
Test test = new Test();
test.f.size = 10;
fixed (char* p = test.f.name) {
PutString("Times New Roman", p, 32);
}
}
}

оператор fixed используется, чтобы фиксировать буфер фиксированного размера структуры, так чтобы его адрес можно было использовать в качестве указателя.

Значение char*, созданное фиксацией экземпляра строки, всегда указывает на строку, оканчивающуюся символом null. В пределах оператора fixed, получающего указатель p на экземпляр строки s, значения указателя в диапазоне от p до p + s.Length - 1 представляют адреса символов в строке, а значение указателя p + s.Length всегда указывает на символ null (символ со значением '\0').

Изменение объектов управляемого типа посредством фиксированных указателей может привести к неопределенному поведению. Например, поскольку строки являются неизменяемыми, на программиста возлагается обязанность обеспечить, чтобы символы, на которые ссылается указатель на фиксированную строку, не изменялись.

Автоматическое завершение строк символом null особенно удобно при вызове внешних API, ожидающих строки «в стиле C». Отметьте, однако, что в экземпляре строки могут содержаться символы null. Если символы null имеются, то строка будет выглядеть усеченной, если обращаться с ней как с завершающейся символом null строкой char*.

Наши рекомендации