Можете ли вы доказать, почему приведение важно, когда я обращаюсь к пустому указателю? - PullRequest
0 голосов
/ 18 октября 2019

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

У меня есть такой пример:

int x;
void* px = &x;
*px = 9;

Можете ли вы доказать, почему это не работает?

Ответы [ 4 ]

3 голосов
/ 18 октября 2019

По определению, указатель void указывает на объект типа «Я не уверен, какой тип объекта».

По определению, когда вы используете унарный оператор * для доступаобъект, на который указывает указатель, вы должны знать (ну, компилятор должен знать), какой тип объекта.

Итак, мы только что доказали, что не можем напрямую разыменовать указатель void, используя *;мы всегда должны явным образом приводить указатель void к какому-либо фактическому типу указателя объекта.

Теперь, в сознании многих людей, «очевидный» ответ на вопрос «на какой тип указывает / должен указывать« общий »указатель?»это "char". И когда-то, до того, как был изобретен тип void, указатели на символы обычно использовались в качестве «общих» указателей. Таким образом, некоторые компиляторы (включая, в частности, gcc) немного расширяют возможности и позволяют вам делать больше (например, арифметику указателей) с указателем void, чем требует стандарт.

Так что может объясните, как такой код в вашем вопросе может "работать". (В вашем случае, тем не менее, поскольку указываемый тип был на самом деле int, а не char, если он "работал", это было только потому, что вы работали на машине с прямым порядком байтов.)

. ... И с учетом сказанного я обнаружил, что код в вашем вопросе не работает для меня, даже в gcc. Сначала он дает мне нефатальное предупреждение:

предупреждение: разыменование указателя 'void *'

Но затем он передумает и решает, что это ошибка:

ошибка: недопустимое использование выражения void

Второй протестированный мной компилятор сказал что-то похожее:

ошибка: неполный тип 'void'не присваивается


Приложение: Еще немного о , почему указательный тип используется повторно при разыменовании указателя:

КогдаВы получаете доступ к указателю, используя *, компилятор будет выдавать код для извлечения (или, возможно, сохранения) в указанном месте. Но компилятору придется испускать код, который обращается к определенному количеству байтов, и во многих случаях может иметь значение, как эти байты интерпретируются. Количество и интерпретация байтов определяются типом (это типы для ), именно поэтому требуется фактический тип, не void.

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

*p + 1

или, что еще лучше,

*p += 1

Если p указывает на char,компилятор, вероятно, будет выдавать какую-то инструкцию addb ("add byte"). Если p указывает на int, компилятор выдаст обычную инструкцию add. Если p указывает на float или double, компилятор будет выдавать инструкцию сложения с плавающей точкой. И так далее.

Но если p - это void *, компилятор понятия не имеет, что делать. Он жалуется (в форме сообщения об ошибке) не только потому, что стандарт C говорит, что вы не можете разыменовать указатель void, но, что более важно, потому что компилятор просто не знает, что делать с вашим кодом.

2 голосов
/ 18 октября 2019

Вкратце:

Целью выражения присваивания должно быть изменяемое значение l 100 * *, которое не может быть выражением void. Это связано с тем, что тип void не представляет каких-либо значений - он обозначает отсутствие значения. Вы не можете создать объект типа void.

Если выражение px имеет тип void *, то выражение *px имеет тип void. Попытка присвоить *px является нарушением ограничения , и компилятор должен кричать на вас за это.

Если вы хотите присвоить новое значение от x до px, то у вас есть для приведения px к int * перед разыменованием:

*((int *)px) = 5;

Глава и стих :

6.2.5 Типы
...
19 voidтип содержит пустой набор значений;это неполный тип объекта, который не может быть завершен.
...
6.3.2.1 L-значения, массивы и обозначения функций
1 lvalue - этовыражение (с типом объекта, отличным от void), которое потенциально обозначает объект ; 64) , если lvalue не определяет объект при его оценке, поведение не определено. Когда говорят, что объект имеет определенный тип, тип определяется значением l, используемым для обозначения объекта. модифицируемое lvalue - это lvalue, которое не имеет типа массива, не имеет неполного типа, не имеет типа с константой, и если это структура или объединение, не имеет какого-либо члена (включая, рекурсивно, любой элемент или элемент всех содержащихся агрегатов или объединений) с типом, определенным const.
...
6.3.2.2 void
1 (несуществующее) значениевыражения void (выражение типа void) ни в коем случае не должно использоваться, и неявные или явные преобразования (кроме void)) не должно применяться к такому выражению. Если выражение любого другого типа оценивается как пустое выражение, его значение или обозначение отбрасывается. (Пустое выражение оценивается для его побочных эффектов.)
...
6.3.2.3 Указатели
1 Указатель на void может быть преобразованили от указателя на любой тип объекта. Указатель на любой тип объекта может быть преобразован в указатель на void и обратно;результат должен сравниться с исходным указателем.
...
6.5.3.2 Операторы адреса и косвенности
...
4 Унарный оператор <strong>*</strong> обозначает косвенность,Если операнд указывает на функцию, результатом является обозначение функции;если он указывает на объект, результатом является lvalue, обозначающее объект. Если операнд имеет тип '' указатель на тип '', результат имеет тип '' тип ''. Если указателю было присвоено недопустимое значение, поведение унарного оператора <strong>*</strong> не определено. 102)
...
6.5.16 Операторы присваивания
...
Ограничения
2 Оператор присваивания должен иметь изменяемое значение lvalue в качестве своего левого операнда.
0 голосов
/ 18 октября 2019

Возможно, он не работает, потому что он нарушает правило в стандарте ISO C, которое требует диагностики, и (я предполагаю) ваш компилятор рассматривает это как фатальную ситуацию.

Согласно ISOC99, как и черновик C11 (n1548), единственным ограничением на использование оператора разыменования * является «[t] операнд унарного оператора * должен иметь тип указателя». [6.5.3.2¶2, n1548] Код, который мы здесь имеем, соответствует этому ограничению и не имеет синтаксической ошибки. Поэтому для использования оператора * не требуется никакой диагностики.

Однако, что означает оператор *, применяемый к указателю void *?

«Унарный оператор * обозначает косвенное обращение. Если операнд указывает на функцию, результатом является указатель функции; если он указывает на объект, результатом является l-значение, обозначающее объект. Если операнд имеет указатель типа '' натип '', результат имеет тип '' тип ''. [6.5.3.2¶4, n1548]

Тип void не является ни функцией, ни типом объекта, поэтому среднее предложение, в котором говорится о создании функции или обозначения объекта, не применимо к нашему случаю. Последнее предложение, приведенное выше , применимо ; оно требует, чтобы выражение, разыменовывающее void *, имело тип void.

Таким образом, *px = 9; садится на мель, потому что присваивает значение int выражению void. Для присваивания требуется выражение lvalue типа объекта; void не является объектом tДа, и выражение, конечно, не является lvalue. Точная формулировка ограничения: «Оператор присваивания должен иметь изменяемое значение l в качестве своего левого операнда». [6.5.16¶2, n1548] Нарушение этого ограничения требует диагностики.

Из моего, возможно, наивного прочтения стандарта следует, что выражение *px как таковое является допустимым;не следует пытаться извлечь из него результат или использовать его в качестве цели назначения. Если это правда, его можно использовать как выражение выражения, значение которого отбрасывается: if (foo()) { *px; }, и его можно избыточно преобразовать в void также: (void) *px. Эти явно бессмысленные ситуации могут каким-то образом использоваться или, по крайней мере, возникать в определенных типах макросов.

Например, если мы хотим быть уверены, что аргумент некоторого макроса является указателем, мы можем воспользоватьсяограничение, что для * требуется операнд-указатель:

#define MAC(NUM, PTR) ( ...  (void) *(PTR) ...)

Т.е. где-то в макросе мы разыменовываем указатель и выбрасываем результат, который будет диагностировать, если PTR не указатель. Похоже, ISO C допускает такое использование, даже если PTR является void *, что, возможно, полезно.

0 голосов
/ 18 октября 2019

Более конкретно, разыменование пустого указателя нарушает формулировку 6.5.3.2 Операторы адреса и косвенного обращения , пункт 4 :

Унарный * операторобозначает косвенность. Если операнд указывает на функцию, результатом является обозначение функции;если он указывает на объект, результатом является lvalue, обозначающее объект. Если операнд имеет тип «указатель на тип», результат имеет тип «тип». Если указателю было присвоено недопустимое значение, поведение унарного оператора * не определено.

Поскольку указатель на void не имеет типа, его нельзя разыменовать. Обратите внимание, что это за пределами неопределенного поведения - это нарушение стандарта языка C.

...