static_cast void* char* против static_cast void** char**

Если я делаю следующее, все в порядке:

char* cp = "abc";
void* vp = NULL;
vp = static_cast<void*>(cp);//ok
cp = static_cast<char*>(vp);//ok

Но нет следующего:

char** cpp = &cp;
void** vpp = NULL;
vpp = static_cast<void**>(cpp);//error C2440: 'static_cast':
                               //cannot convert from 'char **' to 'void **'
cpp = static_cast<char**>(vpp);//error C2440: 'static_cast':
                               //cannot convert from 'void **' to 'char **'

Пожалуйста, может кто-нибудь объяснить мне, почему вторые примеры не разрешены. Пожалуйста, не цитируйте стандарт С++ как весь ответ, потому что я уже видел ответы, в которых он цитируется, и я не понимаю, что они имели в виду. Я хочу понять, почему вторые примеры не работают (т.е. если бы вы могли привести пример, где это было бы опасно, это было бы большим подспорьем). Потому что я не понимаю. Для меня оба примера являются указателями приведения. Почему дополнительный уровень косвенности имеет значение?


person e244    schedule 29.04.2013    source источник
comment
Вы можете неявно преобразовать любой указатель в void * и выполнить статическое приведение в обратном направлении. Но это неверно для T* и U* в целом, которые не связаны между собой. (Теперь подумайте T = char* и U = void*.)   -  person Kerrek SB    schedule 29.04.2013
comment
Вы можете преобразовать char** в void* и наоборот.   -  person Code-Apprentice    schedule 29.04.2013
comment
@Kerrek SB - Да, но почему это запрещено? Когда это будет небезопасно?   -  person e244    schedule 29.04.2013


Ответы (2)


Указатель void * может указывать на «что угодно», и все указатели допустимо преобразовать в void *, и допустимо преобразовать все указатели из void * в какой-либо другой тип.

Однако void ** — это указатель, указывающий на значение void *. А char ** — это указатель, указывающий на значение char *. Эти типы не указывают на типы, которые могут быть преобразованы друг из друга. Вы можете, если вам НУЖНО сделать это, использовать void **vpp = reinterpret_cast<void **>(cpp);, но это «небезопасно» (вы в основном говорите компилятору: «Послушай, я знаю, что я здесь делаю, так что просто делай это», что может не делать то, что вы на самом деле ожидаемо...)

person Mats Petersson    schedule 29.04.2013
comment
Извините, я все еще не понимаю. Если какая-то строка имеет адрес 0x12345678, я могу поместить ее в свой char* или в свой void*. Если адрес моего char* равен 0x87654321, почему я не могу поместить это значение (адрес 0x87654321) в свой void**? т.е. почему можно использовать static_cast 0x12345678 в пустоту*, но нельзя static_cast 0x87654321 в пустоту**? Когда/почему это может быть небезопасно? - person e244; 29.04.2013
comment
@ e244: Проблема в том, что вы можете легко (и без явного приведения) подорвать систему типов, если это преобразование было разрешено. Я предоставил более подробное объяснение в качестве ответа, но краткое описание будет заключаться в том, что двойной указатель позволяет вам изменять данные небезопасным образом. - person David Rodríguez - dribeas; 29.04.2013
comment
Может ли void* указывать на функцию (т. е. можно ли преобразовать указатель функции в void*)? - person rubenvb; 29.04.2013
comment
@rubenvb: не гарантируется стандартом, проблема в том, что на некоторых платформах адресное пространство для кода и данных может иметь разный размер. - person David Rodríguez - dribeas; 29.04.2013

Ограничение состоит в том, чтобы избежать нарушения системы типов. Первое преобразование в порядке:

type *p = ...;
void *vp = p;

Пока вы отдаете тип, вы не можете нанести слишком большой ущерб исходному значению, так как с объектом void мало что можно сделать, а все изменения в vp являются локальными по отношению к указателю и не могут повлиять на него. p.

Если второй случай был разрешен:

type **p = ...;
void **vp = p;

Тогда прекрасно выглядящий и правильный код может сломать ваше приложение. Например:

int *parray[10];
int **p = parray;
void **vp = p;
*vp = new double();  // now parray[0] is a pointer to a double object, 
                     // not a pointer to an int!!!

Система типов была разрушена.

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

person David Rodríguez - dribeas    schedule 29.04.2013