C++. Бархатный путь. Часть 1 - Указатель void *

ОГЛАВЛЕНИЕ

 

Указатель 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.

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

  • после выполнения операции разыменования вычисляется значение (адрес объекта mmm),
  • это значение становится значением выражения,
  • после чего это значение увеличивается на величину, кратную размеру того типа данного, для которого был объявлен указатель.

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

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

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

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