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


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

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

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

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

Подсказка

Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей об истории версий языка C#.

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

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

Примечание.

Для всех операций с указателями требуется небезопасный контекст. Необходимо скомпилировать код, содержащий небезопасные блоки с параметром компилятора 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 указатель типа и y является доступным элементом типа T*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или ulonglong. Сведения о поведении оператора + с указателями см. в разделе Сложение целочисленного значения с указателем или его вычитание из указателя.

В следующем примере показано, как получить доступ к элементам массива с помощью указателя и [] оператора:

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 типа и выражения n типа T* неявно преобразуемого в int, uintlongили 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#:

См. также