Почему я должен переинтерпретировать указатели указателей?

Таким образом, этот static_cast код полностью легален:

int n = 13;
void* pn = static_cast<void*>(&n);
void** ppn = &pn;

Тем не менее, это должно быть преобразовано в reinterpret_cast для компиляции:

int n = 13;
int* foo = &n;
void** bar = static_cast<void**>(&foo);

Если я не изменю его, я получаю сообщение об ошибке:

ошибка C2440: static_cast: невозможно преобразовать из int ** в void ** примечание: типы, на которые указывают, не связаны между собой; для преобразования требуется reinterpret_cast, приведение в стиле C или приведение в стиле функции

Я так понимаю, проблема в том, что "типы не связаны". Тем не менее, я до сих пор не понимаю, если это нормально при переходе от int* к void*, как они могут быть не связаны между собой как int** и void**?


person Jonathan Mee    schedule 21.09.2018    source источник
comment
почему ваш второй фрагмент зачеркнут?   -  person 463035818_is_not_a_number    schedule 21.09.2018
comment
Вы можете видеть &foo как указатель на int*, а bar — как указатель на void*. Эти два типа очень разные. Это дополнительный уровень косвенности, который имеет все значение.   -  person Some programmer dude    schedule 21.09.2018
comment
Возможный дубликат Почему static_cast не может указывать на двойной недействительный указатель?   -  person J. Calleja    schedule 21.09.2018
comment
Обратите внимание, что даже не гарантируется, что sizeof (int*) и sizeof (void*) совпадают.   -  person Ben Voigt    schedule 21.09.2018
comment
@user463035818 user463035818 Выдает ошибку в вопросе. Просто не хотел создавать впечатление, что это функциональный код.   -  person Jonathan Mee    schedule 21.09.2018
comment
@BenVoigt Да... что? Я думал, указатели были одного размера?   -  person Jonathan Mee    schedule 21.09.2018
comment
ее очень тяжело читать. Вопросы обычно имеют ошибочный код, его трудно не заметить, когда в следующей строке написано ... Я получаю сообщение об ошибке. Я никогда не копирую код из вопросов, а скорее из ответов....   -  person 463035818_is_not_a_number    schedule 21.09.2018
comment
@ user463035818 *пожимает плечами* Я удалю это, если вы так думаете. Я просто хотел, чтобы это было ясно.   -  person Jonathan Mee    schedule 21.09.2018
comment
@JonathanMee: ваша конкретная платформа может гарантировать, что все указатели объектов имеют одинаковый размер и представление, но это непереносимое предположение. (На самом деле гарантия очень популярна, поскольку она является частью стандарта POSIX... но более старые архитектуры и системы с голым металлом делают иначе и по-прежнему совместимы с С++)   -  person Ben Voigt    schedule 21.09.2018
comment
@JonathanMee Другой подход, который легче читать, - добавить комментарий к строке, которая вызывает диагностику.   -  person eerorika    schedule 21.09.2018
comment
@BenVoigt Итак ... как это вообще сработает? Я думал, что есть требование, чтобы что-нибудь могло быть передано в и из void*? Если бы void* был другого размера, разве это не аннулировало бы это требование?   -  person Jonathan Mee    schedule 21.09.2018
comment
@JonathanMee нет. Пока void* достаточно велико, чтобы однозначно представлять любое значение указателя, он может работать. Нет причин, по которым T* не может быть меньше void*.   -  person eerorika    schedule 21.09.2018
comment
@JonathanMee: требование туда и обратно может быть выполнено, если указатели без требований к выравниванию (void* и char*) больше, чем указатели с требованиями к выравниванию (включая int*).   -  person Ben Voigt    schedule 21.09.2018
comment
В то же время другие популярные платформы используют тот факт, что выровненные указатели не хранят адресную информацию в младших битах, повторно используя эти биты для других целей, например, указатели функций в ARM используют один бит, чтобы указать, что целевая функция использует набор инструкций Thumb. .   -  person Ben Voigt    schedule 21.09.2018
comment
@BenVoigt Хорошо, я здесь совершенно не по теме. Вы бы предпочли ответить на это в новом вопросе? Я могу открыть один, если вы предпочитаете. Но как бы я вообще использовал что-то вроде fwrite в такой системе; скажем, я пытался вывести массив, как я мог знать, что у void* такой же шаг?   -  person Jonathan Mee    schedule 21.09.2018
comment
static_cast<void*> - это даже не явное преобразование, это неявное преобразование, выполненное явно   -  person curiousguy    schedule 21.09.2018
comment
@JonathanMee: Почему fwrite волнует шаг void*? (Как говорится в ответе 303, на самом деле его нет, математика указателей с void* незаконна) Вы пытаетесь написать массив пустых указателей void* a[]? Вы можете сделать это, но полученный файл не будет переносимым. Значения указателя имеют значение только в одном и том же адресном пространстве, даже чтение файла в той же самой системе после перезапуска будет бесполезным.   -  person Ben Voigt    schedule 21.09.2018


Ответы (2)


int никак не связан с void. То же самое касается int** и void**, поэтому их нельзя преобразовать с помощью static_cast.

void*, однако, является особым. Любой тип указателя данных (включая int*) может быть static_cast в void* и обратно, несмотря на то, что ни один тип не связан с void (более того, преобразование в void* не требует приведения, поскольку оно неявно). int* не имеет этого свойства, как и void**, и ни один другой указатель, кроме void*.


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

void** нельзя дать эти свободы, потому что нельзя дать те же ограничения. Эти ограничения не могут быть введены, потому что void* объектов существуют, и они должны существовать. Если бы мы не могли косвенно или итерировать void**, то мы не могли бы, например, использовать массивы void*.

person eerorika    schedule 21.09.2018

void* pn = static_cast<void*>(&n);

является неявным преобразованием; вы также можете написать

void *pn = &n;

Это означает, что pn хранит указатель на некоторый тип объекта; программист несет ответственность за знание того, что это за тип объекта. Чтобы отбросить, вам нужен static_cast:

int *pi = static_cast<int*>(pn);

Обратите внимание, что использование static_cast для приведения к любому типу, значительно отличающемуся от исходного (float существенно отличается, const int нет), является способом повторной интерпретации. Вы должны написать это reinterpret_cast неявное преобразование (или static_cast), за которым следует static_cast.

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

Возвращаясь к синтаксису объявлений в C и C++:

Объявление указателя на int равно int (*pi); (скобки бесполезны, но помогают проиллюстрировать суть), вы можете прочитать это так: Я объявляю, что выражение (*pi) имеет тип int. Вы можете прочитать объявление функции таким образом: в int f(int i); я заявляю, что если i имеет тип int, то f(i) имеет тип int.

Объявление void (*pi); выглядит как указатель на void, но не существует такой вещи, как объект типа void, выражение *pi даже неправильно сформировано, оно не имеет смысла. Это особый случай в системе типов: синтаксис говорит "указатель на пустоту", семантика говорит "указатель на что-то".

В C и C++ объект-указатель является объектом первого класса, и вы можете взять его адрес и получить указатель на указатель и т. д. (В отличие от Java, где ссылки, подобные другим фундаментальным типам, не являются объектами класса.)

Таким образом, вы можете иметь указатели int**, int***... на (указатели... на int); по той же причине вы можете объявить void**: как указатель на (указатель на пустоту), семантически указатель на (указатель на что-то).

Точно так же указатель на int не может быть назначен указателю на float без приведения:

float *f = &i; // ill-formed

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

person curiousguy    schedule 21.09.2018
comment
Интересно, что reinterpret_cast между типами указателей на самом деле определяется как имеющий тот же результат, что и преобразование в void*, а затем в static_cast. - person Ben Voigt; 22.09.2018