Указатель void *

В C++ существует специальный тип указателя, который называется указателем на неопределённый тип. Для определения такого указателя вместо имени типа используется ключевое слово void в сочетании с описателем, перед которым располагается символ ptrОперации *.

void *UndefPoint;

С одной стороны, объявленная подобным образом переменная также является объектом определённого типа - типа указатель на объект неопределённого типа. В Borland C++ 4.5 имя UndefPoint действительно ссылается на объект размером в 32 бита со структурой, которая позволяет сохранять адреса.

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

Поэтому переменной UndefPoint невозможно присвоить никаких значений без явного преобразования этих значений к определённому типу указателя.

UndefPoint = 0xb8000000; // Такое присвоение недопустимо.

Подобный запрет является вынужденной мерой предосторожности. Если разрешить такое присвоение, то неизвестно, как поступать в случае, когда потребуется изменить значение переменной UndefPoint, например, с помощью операции инкрементации.

UndefPoint++; // Для типа void * нет такой операции…

Эта операция (как и любая другая для типа указатель на объект неопределённого типа) не определена. И для того, чтобы не разбираться со всеми операциями по отдельности, лучше пресечь подобные недоразумения "в корне", то есть на стадии присвоения значения.

Объектам типа указатель на объект неопределённого типа в качестве значений разрешается присваивать значения лишь в сочетании с операцией явного преобразования типа.

В этом случае указатель на объект неопределённого типа становится обычным указателем на объект какого-либо конкретного типа. Со всеми вытекающими отсюда последствиями.

Но и тогда надо постоянно напоминать транслятору о том типе данных, который в данный момент представляется указателем на объект неопределённого типа:

int mmm = 10;
pUndefPointer = (int *)&mmm;
pUndefPointer выступает в роли указателя на объект типа int.
(*(int *)pUndefPointer)++;

Для указателя на объект неопределённого типа не существует способа непосредственной перенастройки указателя на следующий объект с помощью операции инкрементации. В операторе, реализующем операции инкрементации и декрементации, только с помощью операций явного преобразования типа можно сообщить транслятору величину, на которую требуется изменить первоначальное значение указателя.

pUndefPointer++; // Это неверно, инкрементация не определена…
(int *)pUndefPointer++;   // И так тоже ничего не получается…
((int *)pUndefPointer)++; // А так хорошо… Сколько скобок!
++(int *)pUndefPointer;   // И вот так тоже хорошо…

С помощью операции разыменования и с дополнительной операцией явного преобразования типа изменили значение переменной mmm.

pUndefPointer = (int *)pUndefPointer + sizeof(int);
Теперь перенастроили указатель на следующий объект типа int.
pUndefPointer = (int *)pUndefPointer + 1;

И получаем тот же самый результат.

Специфика указателя на объект неопределённого типа позволяет выполнять достаточно нетривиальные преобразования:

(*(char *)pUndefPointer)++;

А как изменится значение переменной mmm в этом случае?

pUndefPointer = (char *)pUndefPointer + 1;

Указатель перенастроился на объект типа char. То есть просто сдвинулся на 1байт.

Работа с указателями на объекты определённого типа не требует такого педантичного напоминания о типе объектов, на которые настроен указатель. Транслятор об этом не забывает.

int * pInt;
int mmm = 10;
pInt = &mmm; // Настроили указатель.
pInt++;      // Перешли к очередному объекту.
*pInt++;     // Изменили значение объекта, идущего следом за
             // переменной mmm.

Напомним, что происходит в ходе выполнения этого оператора.

Операции явного преобразования типов позволяют присваивать указателям в качестве значений адреса объектов типов, отличных от того типа объектов, для которого был объявлен указатель:

int mmm = 10;
char ccc = 'X';
float fff = 123.45;
pInt = &mmm;
pNullInt = (int *)&ccc;
pNullInt = (int *)&fff; // Здесь будет выдано предупреждение об
                        // опасном преобразовании.

Это обстоятельство имеет определённые последствия, которые связаны с тем, что все преобразования над значениями указателей будут производиться без учёта особенностей структуры тех объектов, на которые указатель в самом начале был настроен.

При этом ответственность за результаты подобных преобразований возлагается на программиста.

Назад | Содержание | Вперед