Поделиться через


Операторы, связанные с указателем: получение адреса переменных, расположения хранилища разыменовки и доступ к расположениям памяти

Операторы указателя позволяют принимать адрес переменной (), разыменовывать указатель (&*), сравнивать значения указателя и добавлять или вычитать указатели и целые числа.

Для работы с указателями используются следующие операторы:

Сведения о типах указателей см. в разделе Типы указателей.

Примечание.

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

Оператор address-of >

Унарный оператор & возвращает адрес своего операнда:

unsafe
{
    int number = 27;
    int* pointerToNumber = &number;

    Console.WriteLine($"Value of the variable: {number}");
    Console.WriteLine($"Address of the variable: {(long)pointerToNumber:X}");
}
// Output is similar to:
// Value of the variable: 27
// Address of the variable: 6C1457DBD4

Операнд оператора & должен быть фиксированной переменной. Фиксированные переменные — это переменные в местах хранения, на которые не распространяется сборка мусора. В приведенном выше примере локальная переменная number — это фиксированная переменная, так как она находится в стеке. Переменные, которые находятся в местах хранения, обслуживаемых сборщиком мусора (например, перемещение), называются перемещаемыми переменными. Поля объекта и элементы массива являются примерами перемещаемых переменных. Вы можете получить адрес перемещаемой переменной, если зафиксируете или закрепите ее с помощью инструкции fixed. Полученный адрес действителен только в пределах блока инструкции fixed. В следующем примере показано, как использовать инструкцию fixed и оператор &:

unsafe
{
    byte[] bytes = { 1, 2, 3 };
    fixed (byte* pointerToFirst = &bytes[0])
    {
        // The address stored in pointerToFirst
        // is valid only inside this fixed statement block.
    }
}

Получить адрес константы или значения нельзя.

Дополнительные сведения о фиксированных и перемещаемых переменных см. в разделе Фиксированные и перемещаемые переменные в спецификации языка C#.

Бинарный оператор & вычисляет логическое И своих логических операндов или побитовое логическое И своих целочисленных операндов.

Оператор косвенного обращения указателя *

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

unsafe
{
    char letter = 'A';
    char* pointerToLetter = &letter;
    Console.WriteLine($"Value of the `letter` variable: {letter}");
    Console.WriteLine($"Address of the `letter` variable: {(long)pointerToLetter:X}");

    *pointerToLetter = 'Z';
    Console.WriteLine($"Value of the `letter` variable after update: {letter}");
}
// Output is similar to:
// Value of the `letter` variable: A
// Address of the `letter` variable: DCB977DDF4
// Value of the `letter` variable after update: Z

Оператор нельзя применить * к выражению типа void*.

Бинарный оператор * вычисляет продукт своих числовых операндов.

Оператор доступа к элементу указателя —>

Оператор -> объединяет косвенное обращение к указателю и доступ к члену. То есть если x — это указатель типа T*, а y является доступным членом типа T, выражение формы

x->y

эквивалентно правилу

(*x).y

В следующем примере иллюстрируется использование оператора ->.

public struct Coords
{
    public int X;
    public int Y;
    public override string ToString() => $"({X}, {Y})";
}

public class PointerMemberAccessExample
{
    public static unsafe void Main()
    {
        Coords coords;
        Coords* p = &coords;
        p->X = 3;
        p->Y = 4;
        Console.WriteLine(p->ToString());  // output: (3, 4)
    }
}

Оператор нельзя применить -> к выражению типа void*.

Оператор доступа к элементу указателя []

Для выражения p типа указателя доступ к элементу указателя формы p[n] вычисляется как *(p + n), где n должен иметь тип, неявно преобразуемый в int, uint, long или ulong. Сведения о поведении оператора + с указателями см. в разделе Сложение целочисленного значения с указателем или его вычитание из указателя.

Следующий пример демонстрирует доступ к элементам массива с указателем и оператором []:

unsafe
{
    char* pointerToChars = stackalloc char[123];

    for (int i = 65; i < 123; i++)
    {
        pointerToChars[i] = (char)i;
    }

    Console.Write("Uppercase letters: ");
    for (int i = 65; i < 91; i++)
    {
        Console.Write(pointerToChars[i]);
    }
}
// Output:
// Uppercase letters: ABCDEFGHIJKLMNOPQRSTUVWXYZ

В предыдущем примере выражение stackalloc выделяет блок памяти в стеке.

Примечание.

Оператор доступа к элементу указателя не проверяет ошибки за пределами области.

Невозможно использовать [] для доступа к элементу указателя с выражением типа void*.

Можно также использовать оператор [] для доступа к элементу массива или индексатору.

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

Вы можете выполнить следующие арифметические операции с указателями:

  • Сложение целочисленного значения с указателем или его вычитание из указателя
  • Вычитание двух указателей
  • Инкремент или декремент указателя

Эти операции нельзя выполнять с указателями типа void*.

Сведения о поддерживаемых арифметических операциях с числовыми типами см. в разделе Арифметические операторы.

Сложение целочисленного значения с указателем или его вычитание из указателя

Для указателя p типа T* и выражения n типа, неявно преобразуемого в int, uint, long или ulong, сложение и вычитание определяются следующим образом:

  • Выражения p + n и n + p создают указатель типа T*, полученный добавлением n * sizeof(T) к адресу, предоставленному p.
  • Выражение p - n создает указатель типа T*, полученный вычитанием n * sizeof(T) из адреса, предоставленного p.

Оператор sizeof получает размер типа в байтах.

В следующем примере показано использование оператора + с указателем:

unsafe
{
    const int Count = 3;
    int[] numbers = new int[Count] { 10, 20, 30 };
    fixed (int* pointerToFirst = &numbers[0])
    {
        int* pointerToLast = pointerToFirst + (Count - 1);

        Console.WriteLine($"Value {*pointerToFirst} at address {(long)pointerToFirst}");
        Console.WriteLine($"Value {*pointerToLast} at address {(long)pointerToLast}");
    }
}
// Output is similar to:
// Value 10 at address 1818345918136
// Value 30 at address 1818345918144

Вычитание указателей

Для двух указателей p1 и p2 типа T* выражение p1 - p2 находит разность между адресами, предоставленными p1 и p2, деленную на sizeof(T). Результат имеет тип long. То есть p1 - p2 вычисляется как ((long)(p1) - (long)(p2)) / sizeof(T).

В следующем примере показано вычитание указателей:

unsafe
{
    int* numbers = stackalloc int[] { 0, 1, 2, 3, 4, 5 };
    int* p1 = &numbers[1];
    int* p2 = &numbers[5];
    Console.WriteLine(p2 - p1);  // output: 4
}

Инкремент и декремент указателя

Оператор инкремента ++добавляет 1 к своему операнду указателя. Оператор декремента --вычитает 1 из своего операнда указателя.

Оба оператора поддерживаются в двух формах: постфикс (p++ и p--) и префикс (++p и --p). Результат p++ и p-- — это значение p перед операцией. Результат ++p и --p — это значение p после операции.

В следующем примере показано поведение постфиксных и префиксных операторов инкремента:

unsafe
{
    int* numbers = stackalloc int[] { 0, 1, 2 };
    int* p1 = &numbers[0];
    int* p2 = p1;
    Console.WriteLine($"Before operation: p1 - {(long)p1}, p2 - {(long)p2}");
    Console.WriteLine($"Postfix increment of p1: {(long)(p1++)}");
    Console.WriteLine($"Prefix increment of p2: {(long)(++p2)}");
    Console.WriteLine($"After operation: p1 - {(long)p1}, p2 - {(long)p2}");
}
// Output is similar to
// Before operation: p1 - 816489946512, p2 - 816489946512
// Postfix increment of p1: 816489946512
// Prefix increment of p2: 816489946516
// After operation: p1 - 816489946516, p2 - 816489946516

Операторы сравнения указателей

Вы можете использовать операторы ==, !=, <, >, <= и >= для сравнения операндов любого типа указателя, включая void*. Эти операторы сравнивают адреса, заданные двумя операндами, как если бы они не были назначены целыми числами.

Сведения о поведении этих операторов для операндов других типов см. в статьях Операторы равенства и Операторы сравнения.

Приоритет операторов

В следующем списке операторы, связанные с указателями, упорядочены от самого высокого до самого низкого приоритета:

  • Постфиксные операторы инкремента x++ и декремента x-- и операторы -> и []
  • Префиксные операторы инкремента ++x и декремента --x и операторы & и *
  • Аддитивные операторы + и -
  • Операторы сравнения <, >, <= и >=
  • Операторы равенства == и !=

Используйте скобки (), чтобы изменить порядок вычисления, накладываемый приоритетом операторов.

Полный список операторов C#, упорядоченный по уровню приоритета, можно найти в разделе Приоритет операторов статьи Операторы C#.

Возможность перегрузки оператора

Определяемый пользователем тип не может перегружать связанные операторы &указателя , *и ->[].

Спецификация языка C#

Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:

См. также