Что такое указатель на тип void
10.20 – Указатели void
Указатель void может указывать на объекты любого типа данных:
Однако, поскольку указатель void не знает, на какой тип объекта он указывает, непосредственное косвенное обращение через него невозможно! Указатель void должен быть сначала явно приведен к другому типу указателя, после чего может быть выполнено косвенное обращение через этот новый указатель.
Этот код напечатает:
Следующий очевидный вопрос: если указатель void не знает, на что он указывает, как мы узнаем, к чему его привести? В конечном итоге это зависит от вас.
Вот пример использования указателя void :
Эта программа печатает:
Другие сведения об указателях void
Указатели void могут иметь нулевое значение:
Над указателями void невозможно выполнять арифметику указателей. Это связано с тем, что арифметика указателей требует, чтобы указатель знал размер объекта, на который он указывает, чтобы можно было правильно инкрементировать или декрементировать указатель.
Заключение
В общем, рекомендуется избегать использования указателей void без крайней необходимости, поскольку они позволяют избежать проверки типов. Это позволяет вам непреднамеренно делать вещи, которые бессмысленны, и компилятор не будет жаловаться на это. Например, допустимо следующее:
Но кто знает, каков будет результат!
Хотя приведенная выше функция кажется изящным способом заставить одну функцию обрабатывать несколько типов данных, C++ на самом деле предлагает гораздо лучший способ сделать то же самое (с помощью перегрузки функций), который сохраняет проверку типов, чтобы предотвратить неправильное использование. Многие другие места, где когда-то использовались указатели void для обработки нескольких типов данных, теперь лучше делать с помощью шаблонов, которые также предлагают строгую проверку типов.
Небольшой тест
Вопрос 1
Указатель void (обобщенный указатель) – это указатель, который может указывать на объект любого типа, но не знает, на какой тип объекта он указывает. Для выполнения косвенного обращения указатель void должен быть явно приведен к другому типу указателя.
Указатель null (нулевой указатель) – это указатель, который не указывает на адрес.
Указатель void может быть нулевым указателем.
Что такое указатель на тип void
Ниже на рисунке для иллюстрации всей идеи показана организация памяти некогда очень популярной архитектуры 8051.
Указатель это переменная, которая содержит адрес ячейки памяти. Если, к примеру, адрес ячейки 2050H, то указатель используется для того, чтобы хранить в себе это значение адреса.
Примечание: адрес ячейки памяти это всегда положительное целое число. Диапазон адресов простирается от 0 (адрес первой ячейки памяти; часто этот адрес имеет специальное назначение, об этом позже) до положительной целочисленной константы (которая является адресом последней ячейки памяти).
[Переменные указателей]
Мы можем использовать переменные для хранения адресов памяти, и такие переменные известны как переменные указателей. Обычные переменные используются для хранения в себе значений каких-то данных определенного типа (char, int, float и т. д.). Перед использованием переменной в программе мы сначала её декларируем. Специальным образом нам нужно также декларировать переменные и для указателей – чтобы компилятор знал, что мы декларируем переменную как указатель (это не обычная переменная). Делается такая декларация с помощью оператора *, это так называемый оператор косвенного обращения на языке C (indirection operator), иногда его называют оператором разыменования.
Общий синтаксис декларации указателя следующий:
Здесь мы декларировали переменную указателя с именем ptr, и этот указатель предназначен для указания на первую ячейку памяти, где находится значение типа int.
Почему для указателей нужно указывать тип данных? По некоторому адресу в памяти могут содержаться какие-то данные, и это понятно. И это может быть данные любого типа char, int, float, даже структура, и т. д. Разница между типами в том, что они могут занимать для себя разное количество памяти. Char требует 1 байт, int может требовать 2 байта (хотя это не всегда так), и float занимает 4 байта. Память, выделенная для всех этих типов это последовательность из нескольких непрерывно следующих друг за другом байт.
Давайте рассмотрим сценарий наподобие следующего, в программе определены 3 переменные:
Предположим, что память системы начинается с адреса 2000H. Теперь символьная переменная ‘a’ будет находиться по адресу 2000H (и займет в памяти 1 байт), тогда как int-переменная ‘b’ займет 2 байта и будет находиться по адресам 2001H и 2002H. И наконец, последняя float-переменная ‘c’ займет 4 байта, и они будут находится в расположенных друг за другом байтах памяти с адресами 2003H, 2004H, 2005H, 2006H. Теперь Вы можете догадаться, зачем надо указывать типы данных, чтобы объявить переменную указателя. Причина в том, что области памяти под переменные выделяются в последовательных, находящихся друг за другом байтах памяти, и количество выделенных байт зависит от типа переменной, которая в них содержится.
Примечание: показанное в этом примере распределение памяти типично для 8-разрядных систем, таких как MSC-51 и AVR. Для 16-битных и 32-разрядных систем реальное распределение памяти для переменных может быть другим, что связано с выравниванием данных в памяти с целью более эффективного использования особенностей процессора.
Таким образом, когда мы декларируем переменную указателя как float *ptr, и затем присваиваем ей адрес обычной float-переменной c, то для компилятора устанавливается привязка указателя ptr ко всей области памяти от 2003H до 2006H. Но сама переменная ptr будет хранить в себе только начальный адрес блока памяти переменной (т. е. в нашем примере это 2003H), а тип указателя будет указывать для компилятора размер этого блока.
Следовательно, чтобы компилятор мог корректно интерпретировать содержимое памяти, соответствующее указателю, для указателя должен быть при декларации указан тип данных. И этот тип данных должен совпадать с типом данных, которые находятся по адресу переменной – тому адресу, который присваивается переменной указателя. Например, если адрес 2000H будет присвоен указателю ptr, то указателю будет соответствовать память, в которой находится символьная переменная ‘a’. В этом случае переменная указателя ptr должна быть декларирована с типом char, как показано ниже:
Примечание: фактически мы могли бы декларировать переменную указателя без какого либо типа данных, используя ключевое слово void. Тогда получится так называемый пустой указатель.
[Присваивание адреса переменной указателя]
Чтобы можно было использовать указатель и его возможности, указателю должен быть присвоен адрес переменной. Указателю можно присвоить адрес как одиночной переменной, так и адрес массива, так и адрес структуры, и адрес переменной класса, и даже адрес переменной указателя. Это делает указатели особенно мощным (но и достаточно опасным при неумелом использовании) инструментом в программировании на языке C. Мы можем играючи обращаться с памятью системы, используя указатели.
Чтобы присвоить адрес переменной указателя мы используем оператор & (оператор взятия адреса переменной). Оператор & возвратит начало места в памяти, где расположена переменная. Пример:
[Обращение к содержимому памяти по указателю]
Теперь мы знаем, как присваивать адрес переменной указателя. Но как можно в программе обратиться к содержимому переменной, адрес которой присвоен указателю? Для этого мы используем тот же оператор косвенного обращения *, который мы использовали для декларации переменной указателя. Операция взятия значения по указателю также называется разыменованием указателя.
Запуск этого кода выведет следующую строку:
[Арифметические операции над указателями]
[Указатели void на языке C]
Обычно переменная указателя декларируется с указанием типа данных содержимого, которое хранится в том месте памяти, на которое ссылается указатель (перевод статьи [2]). Примеры:
Переменная указателя, декларированная на определенный тип, не может содержать в себе адрес переменной другого типа. Это неправильно, и приведет к сообщению об ошибке при компиляции. Пример:
На языке C есть возможность создать указатель на неопределенный тип, так называемый «пустой указатель» (void pointer). Указатель на void это просто переменная указателя, которая декларирована с зарезервированным на языке C ключевым словом void. Пример:
Когда указатель декларируется с ключевым словом void, он становится универсальным. Это значит, что ему может быть присвоен адрес переменной любого типа (char, int, float и т. д.), и это не будет ошибкой.
[Разыменование void-указателя]
Как делается разыменование типизованных указателей с помощью оператора *, Вы уже знаете (если нет, то см. врезку «Что такое указатель»). Но в случае указателя на void нужно использовать приведение типа (typecast) переменной указателя, чтобы выполнить её разыменование (выполнить обращение к содержимому памяти, на которую ссылается void-указатель). Причина в том, что с void-указателем не связан никакой тип, и для компилятора нет никакого способа автоматически узнать, как обращаться к содержимому памяти, связанному с void-указателем. Таким образом, чтобы получить данные, на который ссылается void-указатель, мы делаем приведение указателя к корректному типу данных, которые находятся по адресу, содержащемуся в void-указателе.
Указатели void полезны для программиста, когда заранее неизвестно о типе данных, которые поступают на вход программы. Типичным примером могут служить библиотечные функции манипулирования блоками памяти memcpy, memset и т. п. С помощью void-указателя программист может сослаться на место размещения данных произвольного, заранее неизвестного типа данных. Программа, к примеру, может быть написана таким образом, чтобы запросить у пользователя, какое приведение типа нужно использовать, чтобы правильно обработать входные данные. Ниже приведен в качестве примера кусок подобного кода.
При использовании void-указателей следует помнить, что для них недопустимы арифметические операции, как для типизованных указателей (см. врезку «Что такое указатель»). Пример:
Что такое указатель на тип void
В C++ существует специальный тип указателя, который называется указателем на неопределённый тип. Для определения такого указателя вместо имени типа используется ключевое слово void в сочетании с описателем, перед которым располагается символ ptrОперации *.
Но, с другой стороны, для объекта типа указатель на объект неопределённого типа отсутствует информация о размерах и внутренней структуре адресуемого участка памяти. Из-за этого не могут быть определены какие-либо операции для преобразования значений.
Поэтому переменной UndefPoint невозможно присвоить никаких значений без явного преобразования этих значений к определённому типу указателя.
Подобный запрет является вынужденной мерой предосторожности. Если разрешить такое присвоение, то неизвестно, как поступать в случае, когда потребуется изменить значение переменной UndefPoint, например, с помощью операции инкрементации.
Эта операция (как и любая другая для типа указатель на объект неопределённого типа) не определена. И для того, чтобы не разбираться со всеми операциями по отдельности, лучше пресечь подобные недоразумения «в корне», то есть на стадии присвоения значения.
Объектам типа указатель на объект неопределённого типа в качестве значений разрешается присваивать значения лишь в сочетании с операцией явного преобразования типа.
В этом случае указатель на объект неопределённого типа становится обычным указателем на объект какого-либо конкретного типа. Со всеми вытекающими отсюда последствиями.
Но и тогда надо постоянно напоминать транслятору о том типе данных, который в данный момент представляется указателем на объект неопределённого типа:
Для указателя на объект неопределённого типа не существует способа непосредственной перенастройки указателя на следующий объект с помощью операции инкрементации. В операторе, реализующем операции инкрементации и декрементации, только с помощью операций явного преобразования типа можно сообщить транслятору величину, на которую требуется изменить первоначальное значение указателя.
С помощью операции разыменования и с дополнительной операцией явного преобразования типа изменили значение переменной mmm.
И получаем тот же самый результат.
Специфика указателя на объект неопределённого типа позволяет выполнять достаточно нетривиальные преобразования:
А как изменится значение переменной mmm в этом случае?
Указатель перенастроился на объект типа char. То есть просто сдвинулся на 1байт.
Работа с указателями на объекты определённого типа не требует такого педантичного напоминания о типе объектов, на которые настроен указатель. Транслятор об этом не забывает.
Операции явного преобразования типов позволяют присваивать указателям в качестве значений адреса объектов типов, отличных от того типа объектов, для которого был объявлен указатель:
Это обстоятельство имеет определённые последствия, которые связаны с тем, что все преобразования над значениями указателей будут производиться без учёта особенностей структуры тех объектов, на которые указатель в самом начале был настроен.
При этом ответственность за результаты подобных преобразований возлагается на программиста.
C++. Бархатный путь. Часть 1
Страница 65. Указатель void *
Указатель void *
В C++ существует специальный тип указателя, который называется указателем на неопределённый тип. Для определения такого указателя вместо имени типа используется ключевое слово void в сочетании с описателем, перед которым располагается символ ptrОперации *.
Но, с другой стороны, для объекта типа указатель на объект неопределённого типа отсутствует информация о размерах и внутренней структуре адресуемого участка памяти. Из-за этого не могут быть определены какие-либо операции для преобразования значений.
Поэтому переменной 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;
И получаем тот же самый результат.
Специфика указателя на объект неопределённого типа позволяет выполнять достаточно нетривиальные преобразования:
А как изменится значение переменной 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; // Здесь будет выдано предупреждение об // опасном преобразовании.
Это обстоятельство имеет определённые последствия, которые связаны с тем, что все преобразования над значениями указателей будут производиться без учёта особенностей структуры тех объектов, на которые указатель в самом начале был настроен.
При этом ответственность за результаты подобных преобразований возлагается на программиста.
Что такое указатель на тип void
void — тип, спецификатор типа и ключевое слово в языках программирования Си, Си++, Java, C# и D, заимствованное у их общего предшественника Алгола 68.
Содержание
История
Среди современных языков программирования ключевое слово void впервые появилось в Си++ для поддержки концепции обобщенных указателей. Тем не менее, благодаря скорому заимствованию у Си++, первым нормативным документом, содержащим это ключевое слово стал стандарт языка Си, опубликованный ANSI в 1989г. В рамках языка Си++ void был стандартизован в 1998г.
Впоследствии ключевое слово void и связанные с ним языковые конструкции были унаследованы языками Java и C#, D.
Синтаксис
Синтаксически, void является одним из спецификаторов типа, входящих в более общую группу спецификаторов объявления.
Семантика
Семантика ключевого слова void не подчиняется общей семантике спецификаторов типа и зависит от способа употребления:
Тип void определен как неполный тип, который не может быть дополнен. Как следствие, этот тип не должен быть использован там, где допускаются только полные типы, например, в качестве типа параметра в определении функции.
Язык Си до введения void
Схожим образом, определения функций, не имеющие параметров записывались с пустыми круглыми скобками:
Примеры
Ссылки
Полезное
Смотреть что такое «Void» в других словарях:
void — 1 / vȯid/ adj 1: of no force or effect under law a void marriage 2: voidable void·ness n void 2 vt: to make or declar … Law dictionary
Void — Pour la commune française, voir Void Vacon. En programmation, void est un mot clé que l on retrouve dans le langage C et plusieurs autres langages de programmation dont il est à l origine, comme le C++, le C# ou le Java. Ce mot clé void… … Wikipédia en Français
Void — Void, a. [OE. voide, OF. voit, voide, vuit, vuide, F. vide, fr. (assumed) LL. vocitus, fr. L. vocare, an old form of vacare to be empty, or a kindred word. Cf.
void — [vɔɪd] adjective LAW a contract or agreement that is void has no legal effect because it is against the law: • Under state law, a contract to pay money knowingly lent for gambling is void. void verb [transitive] : • Mr. Mullen s termination… … Financial and business terms
Void — Void, v. t. [imp. & p. p.
Void — steht für: Void (Astronomie), eine astronomische Struktur mit sehr wenig Materie und damit sehr geringer Dichte Void (Verbindungstechnik) Void (Schlüsselwort), ein Schlüsselwort in einigen Programmiersprachen Void Vacon, eine französische… … Deutsch Wikipedia
void — [adj1] empty abandoned, bare, barren, bereft, clear, deprived, destitute, devoid, drained, emptied, free, lacking, scant, short, shy, tenantless, unfilled, unoccupied, vacant, vacuous, without; concepts 481,583,740,774 Ant. filled, full, occupied … New thesaurus
void — [void] adj. [ME voide < OFr vuide < VL * vocitus, for L vocivus, var. of vacivus < vacare, to be empty] 1. not occupied; vacant: said of benefices, offices, etc. 2. a) holding or containing nothing b) devoid or destitute (of) [void of… … English World dictionary
VOiD — es una forma de diseñar un sistema operativo en ausencia de kernel. El antikernel VOiD fue diseñado por un joven hacker llamado Matias Leiva. Consiste en un grupo de cells cargadas dinámicamente, su comportamiento es totalmente descentralizado.… … Wikipedia Español