Косвенное обращение по указателю

Выражение_косвенного_обращения_по_указателю состоит из звездочки (*) с последующим унарным_выражением.

pointer-indirection-expression:
* unary-expression

Унарный оператор * означает косвенное обращение по указателю и используется для получения переменной, на которую указывает указатель. Результатом вычисления *P, где P — это выражение типа указателя T*, является переменная типа T. Применение унарного оператора * к выражению типа void* или к выражению не типа указателя является ошибкой времени компиляции.

Результат применения унарного оператора * к указателю со значением null определяется реализацией. В частности нет гарантии, что для этой операции будет создано исключение System.NullReferenceException.

Если указателю присвоено недопустимое значение, поведение унарного оператора * является неопределенным. Среди недопустимых значений для разыменования указателя унарным оператором * неправильно выровненный адрес указываемого типа (см. пример в §18.4) и адрес переменной после окончания ее времени жизни.

Для целей анализа определенного присваивания переменная, полученная вычислением выражения вида *P, считается изначально присвоенной (§5.3.1).

Доступ к члену по указателю

Доступ_к_члену_по_указателю состоит из первичного_выражения, за которым следует лексема "->", а затем идентификатор и необязательный список_аргументов_типа.

pointer-member-access:
primary-expression -> identifier type-argument-listopt

В доступе к члену по указателю вида P->I, P должно быть выражением типа указателя, отличного от void*, а I должно обозначать доступный член того типа, на который указывает P.

Доступ к члену по указателю вида P->I вычисляется точно так же, как (*P).I. Описание оператора косвенного обращения по указателю (*) см. в §18.5.1. Описание оператора доступа к члену (.) см. в §7.6.4.

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

using System;

struct Point
{
public int x;
public int y;

public override string ToString() {
return "(" + x + "," + y + ")";
}
}

class Test
{
static void Main() {
Point point;
unsafe {
Point* p = &point;
p->x = 10;
p->y = 20;
Console.WriteLine(p->ToString());
}
}
}

оператор -> используется для доступа к полям и вызова метода структуры с помощью указателя. Поскольку операция P->I совершенно эквивалентна (*P).I, метод Main можно было записать так:

class Test
{
static void Main() {
Point point;
unsafe {
Point* p = &point;
(*p).x = 10;
(*p).y = 20;
Console.WriteLine((*p).ToString());
}
}
}

Доступ к элементу по указателю

Доступ_к_элементу_по_указателю состоит из первичного_выражения_создания_не_массива, за которым следует выражение, заключенное в скобки "[" и "]".

pointer-element-access:
primary-no-array-creation-expression [ expression ]

В выражении доступа к элементу по указателю P[E], P должно быть выражением типа указателя, отличного от void*, а E должно быть выражением, которое может быть неявно преобразовано к типу int, uint, long или ulong.

Доступ к элементу по указателю вида P[E] вычисляется точно так же, как *(P + E). Описание оператора косвенного обращения по указателю (*) см. в §18.5.1. Описание оператора сложения указателей (+) см. в §18.5.6.

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

class Test
{
static void Main() {
unsafe {
char* p = stackalloc char[256];
for (int i = 0; i < 256; i++) p[i] = (char)i;
}
}
}

доступ к элементу по указателю используется для инициализации символьного буфера в цикле for. Поскольку операция P[E] совершенно эквивалентна *(P + E), этот пример можно было записать так:

class Test
{
static void Main() {
unsafe {
char* p = stackalloc char[256];
for (int i = 0; i < 256; i++) *(p + i) = (char)i;
}
}
}

Оператор доступа к элементу по указателю не проверяет на ошибки выхода за допустимые границы, и поведение при доступе к элементу вне границ является неопределенным. Это так же, как в C и C++.

Оператор адреса

Выражение_addressof состоит из амперсанда (&), за которым следует унарное_выражение.

addressof-expression:
& unary-expression

Если дано выражение E типа T и классифицируемое как фиксированная переменная (§18.3), конструкция &E вычисляет адрес переменной, заданной выражением E. Типом результата является T*, классифицируемый как значение. Вызывается ошибка времени компиляции, если E не классифицируется как переменная, если E классифицируется как локальная переменная только для чтения или если E обозначает перемещаемую переменную. В этом последнем случае можно использовать оператор fixed (§18.6), чтобы временно "фиксировать" переменную перед получением ее адреса. Как изложено в §7.6.4, вне конструктора экземпляров или статического конструктора для структуры или класса, определяющего поле readonly, это поле считается значением, а не переменной. Его адрес, как таковой, нельзя получить. Аналогично и адрес константы получить нельзя.

Оператор & не требует, чтобы его аргументы были определенно присвоены, но после операции & та переменная, к которой применен этот оператор, считается определенно присвоенной на пути выполнения, на котором происходит эта операция. На программиста возлагается обязанность обеспечить, чтобы правильная инициализация переменной фактически происходила в такой ситуации.

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

using System;

class Test
{
static void Main() {
int i;
unsafe {
int* p = &i;
*p = 123;
}
Console.WriteLine(i);
}
}

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

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

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