Фиксированные и перемещаемые переменные
Оператор взятия адреса (§18.5.4) и оператор fixed (§18.6) разделяют переменные на две категории: фиксированные переменные и перемещаемые переменные.
Фиксированные переменные находятся в местах хранения, не затрагиваемых действием сборщика мусора. (Примерами фиксированных переменных являются локальные переменные, параметры по значению и переменные, созданные разыменованием указателей.) А перемещаемые переменные находятся в местах хранения, которые могут быть перемещены или удалены сборщиком мусора. (Примерами перемещаемых переменных являются поля в объектах и элементы массивов.)
Оператор & (§18.5.4) дает возможность без ограничений получать адрес фиксированной переменной. Но поскольку перемещаемая переменная может быть перемещена или удалена сборщиком мусора, адрес перемещаемой переменной можно получить только с помощью оператора fixed (§18.6), и этот адрес остается действительным только на время действия этого оператора fixed.
Точнее говоря, фиксированной переменной является одно из следующего:
· переменная, полученная из простого_имени (§7.6.2), относящегося к локальной переменной или параметру по значению, если только эта переменная не захвачена анонимной функцией;
· переменная, полученная в результате доступа_к_члену (§7.6.4) вида V.I, где V — фиксированная переменная типа_структуры;
· переменная, полученная в результате выражения_косвенного_обращения_по_указателю (§18.5.1) вида *P, доступа_к_члену_по_указателю (§18.5.2) вида P->I или доступа_к_элементу_по_указателю (§18.5.3) вида P[E].
Все другие переменные классифицируются как перемещаемые.
Обратите внимание, что статическое поле классифицируется как перемещаемая переменная. Также обратите внимание, что параметр ref или out классифицируется как перемещаемая переменная, даже если аргумент, предоставленный для параметра, является фиксированной переменной. И наконец, обратите внимание, что переменная, созданная разыменованием указателя, всегда классифицируется как фиксированная переменная.
18.4 Преобразования указателей
В небезопасном контексте набор доступных неявных преобразований (§6.1) расширен за счет включения следующих неявных преобразований указателей:
· от любого типа_указателя к типу void*.
· от литерала null к любому типу_указателя.
Кроме того, в небезопасном контексте набор доступных явных преобразований (§6.2) расширен за счет включения следующих явных преобразований указателей:
· от любого типа_указателя к любому другому типу_указателя;
· Из типа sbyte, byte, short, ushort, int, uint, long или ulong в любой тип указателя.
· Из любого типа указателя в тип sbyte, byte, short, ushort, int, uint, long или ulong.
И наконец, в небезопасном контексте набор стандартных неявных преобразований (§6.3.1) включает следующее преобразование указателя:
· от любого типа_указателя к типу void*.
Преобразования между двумя типами указателей никогда не изменяют фактическое значение указателя. Иначе говоря, преобразование от одного типа указателя к другому не влияет на основной адрес, задаваемый указателем.
При преобразовании одного типа указателя к другому, если полученный указатель неправильно выровнен для указываемого типа, поведение является неопределенным, если результат разыменован. В общем, понятие "правильно выровненный" является транзитивным: если указатель на тип A правильно выровнен для указателя на тип B, который в свою очередь правильно выровнен для указателя на тип C, то указатель на тип A правильно выровнен для указателя на тип C.
Рассмотрите следующий случай, когда доступ к переменной одного типа выполняется через указатель на другой тип:
char c = 'A';
char* pc = &c;
void* pv = pc;
int* pi = (int*)pv;
int i = *pi; // undefined
*pi = 123456; // undefined
При преобразовании типа указателя к указателю на байт результат указывает на младший адресуемый байт переменной. Последовательные приращения результата до размера переменной дают указатели на остальные байты этой переменной. Например, следующий метод отображает каждый из восьми байт переменной типа double в виде шестнадцатеричного значения:
using System;
class Test
{
unsafe static void Main() {
double d = 123.456e23;
unsafe {
byte* pb = (byte*)&d;
for (int i = 0; i < sizeof(double); ++i)
Console.Write("{0:X2} ", *pb++);
Console.WriteLine();
}
}
}
Конечно, производимый вывод зависит от порядка следования байтов.
Сопоставления между указателями и целыми определяются реализацией. Однако на архитектурах 32- и 64-разрядных ЦП с линейным адресным пространством преобразования указателей к целым типам и целых типов к указателям обычно происходит точно так же, как преобразования значений типа uint или ulong соответственно к этим целым типам или от них к указателям.
Массивы указателей
В небезопасном контексте можно формировать массивы указателей. В массивах указателей разрешены лишь некоторые преобразования, применимые к другим типам массива:
· Неявное преобразование ссылочного типа (§6.1.6) из любого типа_массива в System.Array и реализуемые им интерфейсы применимо и к массивам указателей. Однако любая попытка получить доступ к элементам массива с помощью System.Array или реализуемых им интерфейсов приведет к исключению времени выполнения, так как типы указателей не могут быть преобразованы в object.
· Явные и неявные преобразования ссылочных типов (§6.1.6, §6.2.4) из типа одномерных массивов S[] в System.Collections.Generic.IList<T> и его универсальные базовые интерфейсы неприменимы к массивам указателей, поскольку типы указателей не могут использоваться в качестве аргументов типа, а преобразования из типов указателей в типы, не являющиеся типами указателей, невозможны.
· Явное преобразование ссылочного типа (§6.2.4) из System.Array и реализуемых им интерфейсов в любой тип_массива применимо к массивам указателей.
· Явное и неявное преобразование ссылочных типов (§6.2.4) из System.Collections.Generic.IList<S> и его базовых интерфейсов в тип одномерных массивов T[] не применимо к массивам указателей, поскольку типы указателей не могут использоваться в качестве аргументов типа, а преобразования из типов указателей в типы, не являющиеся типами указателей, невозможны.
Эти ограничения означают, что расширение оператора foreach на массивы, описанные в §8.8.4, не применимы к массивам указателей. Вместо этого оператор foreach, заданный в виде
foreach (V v in x) внедренный оператор
, где x имеет тип массива вида T[,,…,], n — число измерений минус 1, а T или V имеют тип указателя, развернут с помощью вложенных циклов оператора for:
{
T[,,…,] a = x;
V v;
for (int i0 = a.GetLowerBound(0); i0 <= a.GetUpperBound(0); i0++)
for (int i1 = a.GetLowerBound(1); i1 <= a.GetUpperBound(1); i1++)
…
for (int in = a.GetLowerBound(n); in <= a.GetUpperBound(n); in++) {
v = (V)a.GetValue(i0,i1,…,in);
внедренный_оператор
}
}
Переменные a, i0, i1, … in невидимы или недоступны для x, внедренного_оператора или любого другого исходного кода программы. Переменная v доступна только для чтения во внедренном операторе. Если не определено явное преобразование (§18.4) типа T (типа элементов) в V, возникает ошибка, и больше никакие действия не выполняются. Если значение x равно null, во время выполнения возникает исключение System.NullReferenceException.
Указатели в выражениях
В небезопасном контексте выражение может давать результат типа указателя, но вне небезопасного контекста выражение с типом указателя вызывает ошибку времени компиляции. Говоря точно, ошибка времени компиляции вне небезопасного контекста вызывается в случае, когда любое простое_имя (§7.6.2), доступ_к_члену (§7.6.4), выражение_вызова (§7.6.5) или доступ_к_элементу (§7.6.6) имеет тип указателя.
В небезопасном контексте порождения первичного_выражения_создания_не_массива (§7.6) и унарного_выражения (§7.7) дают возможность создавать следующие дополнительные конструкции:
primary-no-array-creation-expression:
...
pointer-member-access
pointer-element-access
sizeof-expression
unary-expression:
...
pointer-indirection-expression
addressof-expression
Эти конструкции описаны в следующих разделах. Приоритет и ассоциативность небезопасных операторов подразумеваются грамматикой.