Для указателей поддерживаются неявные преобразования из любого типа указателя к типу void*. Любому указателю можно присвоить константу null. Кроме того, допускаются явные преобразования:
□ между указателями любого типа;
□ между указателями любого типа и целыми типами (со знаком и без знака).
Корректность преобразований лежит на совести программиста. Преобразования никак не влияют на величины, на которые ссылаются указатели, но при попытке получения значения по указателю несоответствующего типа поведение программы не определено1.
Инициализация указателей
Ниже перечислены способы присваивания значений указателям:
1. Присваивание указателю адреса существующего объекта:
- с помощью операции получения адреса:
int a = 5; // целая переменная
int* р = &а; //в указатель записывается адрес а
ПРИМЕЧАНИЕ
Программист должен сам следить за тем, чтобы переменные, на которые ссылается указатель, были правильно инициализированы.
- с помощью значения другого инициализированного указателя.
int* r = р;
- с помощью имени массива, которое трактуется как адрес:
int[] b = new int[] {10, 20, 30, 50}; // массив
fixed (int* t = b){...};// присваивание адреса начала массива
fixed (int* t = &b[0]){...}; // то же самое
ПРИМЕЧАНИЕ
Оператор fixed рассматривается позже.
2. Присваивание указателю адреса области памяти в явном виде:
char* v = (char *)0x12F69E;
Здесь 0xl2F69E — шестнадцатеричная константа, (char *) — операция приведения типа: константа преобразуется к типу указателя на char.
ПРИМЕЧАНИЕ
Использовать этот способ можно только в том случае, если адрес вам точно известен, в противном случае может возникнуть исключение.
3. Присваивание нулевого значения:
int* хх = null;
Как правило, если в документации встречается оборот «неопределенное поведение* (undefined behavior), ничего хорошего это не сулит.
4. Выделение области памяти в стеке и присваивание ее адреса указателю: int* s = stackalloc int [10];
Здесь операция stackalloc выполняет выделение памяти под 10 величин типа int (массив из 10 элементов) и записывает адрес начала этой области памяти в переменную s, которая может трактоваться как имя массива.
ПРИМЕЧАНИЕ
Специальных операций для выделения области динамической памяти (хипа) в С# не предусмотрено. В случае необходимости можно использовать, например, системную функцию НеарAllос (пример см. в спецификации языка).