Я видел и использовал это много раз в C ++, особенно в различных реализациях потоков. Что мне интересно, есть ли какие-либо подводные камни / проблемы с этим? Есть ли какой-нибудь способ, которым мы могли бы столкнуться с ошибкой или неопределенным состоянием, когда мы приводим к void * и обратно? Как мы должны решить такие проблемы, если они есть?
Спасибо.
Что мне интересно, есть ли какие-либо подводные камни / проблемы с этим?
Вы должны быть абсолютно уверены во время void*
вернуться к конкретному типу, если вы этого не сделаете, вы получите Неопределенное поведение и потенциальная катастрофа. Как только вы используете void *
ты проиграл тип безопасности.Сложно отследить, какого типа void *
на самом деле указывает на то, что нет способа гарантировать или определить, что он действительно указывает на тип, к которому вы собираетесь привести его обратно.
Есть ли какой-нибудь способ, которым мы могли бы столкнуться с ошибкой или неопределенным состоянием, когда мы приводим к void * и обратно?
Да, сценарий, упомянутый в #1
,
Как мы должны решить такие проблемы, если они есть?
Избегать использования void *
в C ++ полностью, вместо этого используйте шаблоны и наследование.
В Си вам может понадобиться это в определенных ситуациях, но постарайтесь свести его использование к минимуму.
Нижняя линия,
C / C ++ позволяет вам выстрелить себе в ногу, решать вам или нет.
я имею не Видно, что в C ++ много разыгрывается. Это была практика в C, которую активно избегали в C ++.
Приведение к пустоте * удаляет все типы безопасности.
Если вы используете reinterpret_cast
или же static_cast
привести от типа указателя к void*
и вернуться к тому же типу указателя, на самом деле стандарт гарантирует, что результат будет четко определен.
Опасность в том, что вы можете разыграть void*
к неправильному типу, так как вы больше не уверены, какой был правильный тип.
Я знаю много функций в драйвере и т. Д., Использующих указатели void для возврата данных вызывающей стороне, схема в основном такая же:
int requestSomeData(int kindOfData, void * buffer, int bufferSize);
Эта функция может принимать разные типы данных в качестве параметра.
Что они делают, так это используют bufferSize в качестве параметра, чтобы избежать записи в места памяти, в которые они не должны писать.
Если bufferSize не совпадает или меньше, чем данные, которые должны быть возвращены, функция возвратит код ошибки.
В любом случае: избегайте их использования или думайте трижды, прежде чем писать какой-либо код.
Единственное, что стандартные гранты это то, что, учитывая A* pa
, (A*)(void*)pA == pA
,
Следствие
void* pv = pA;
A* pA2 = (A*)pv;
pA2->anything ...
будет так же, как pA->anything ...
Все остальное «не определено», фактически — как-то зависит от реализации.
Исходя из моего опыта, вот некоторые известные подводные камни:
A
производная форма B
, pA
а также pB
быть A*
а также B*
, pB=pA
марки pB
указать на основание A
, Это не значит, что pB
а также pA
один и тот же адрес. следовательно pB = (B*)(void*)pA
на самом деле может указывать куда-либо еще на A (хотя объекты с единым наследованием обычно реализуются с одним и тем же источником, поэтому, очевидно, работает нормально)pB
на самом деле указывает на A
, pA = (A*)(void*)pB
не обязательно указывать правильно на объект. Правильный путь pA = static_cast<A*>(pB);
class A: public Z, public B { ... };
если Z
не пусто, учитывая A
, B
субкомпонент не будет иметь тот же адрес. (и множественное наследование в C ++ везде, где есть iostream)(char*)(void*)pI
(где pI
указывает на целое число) не будет таким же, как «*pI
если *pI
в (-128 .. + 127) «(это будет только на машинах с прямым порядком байтов)В общем не предполагайте, что преобразование между типами работает, просто меняя способ интерпретации адреса.