Приведение к void* и обратно к Original_Data_Type*

Я видел и использовал это много раз на С++, особенно в различных реализациях потоков. Что мне интересно, есть ли какие-либо подводные камни/проблемы при этом? Есть ли способ, которым мы могли бы столкнуться с ошибкой или неопределенным условием, когда мы приводим к void* и обратно? Как нам решать такие вопросы, если они есть?

Спасибо.


person Izza    schedule 05.09.2012    source источник
comment
Как нам решать такие вопросы, если они есть? не используйте void*...   -  person Luchian Grigore    schedule 05.09.2012
comment
void* — довольно мощная конструкция, но столь же опасная, потому что просто невозможно сказать, почему что-то идет не так. вы просто должны быть ОЧЕНЬ осторожны, когда используете его.   -  person Andreas Grapentin    schedule 05.09.2012
comment
Не могли бы вы привести пример (код), где вы это видели? Я могу вспомнить случаи, когда вы должны сделать это таким образом. Но, как правило, избегайте использования void*, где это возможно.   -  person Axel    schedule 05.09.2012
comment
В наши дни вы обычно можете использовать стандартную библиотеку потоков (или аналогичную библиотеку Boost, если она недоступна), а не различные реализации потоков. Это не требует никаких хитрых преобразований указателя в пользовательском коде.   -  person Mike Seymour    schedule 05.09.2012
comment
В прошлый раз, когда я читал, я пообещал сделать что-то ужасное, когда столкнусь с этим в следующий раз. Жаль, что я не помню, что это было. Кроме того, вам даже не нужен приведение — любой указатель, не являющийся функцией, можно присвоить void *.   -  person    schedule 21.09.2013
comment
Выглядит действительным. Читать это   -  person smac89    schedule 21.09.2013
comment
если он компилируется, он действителен и IMO должен компилироваться. Но мне не нравится эта смесь C и C++...   -  person alexbuisson    schedule 21.09.2013
comment
Пожалуйста. Пожалуйста. Пожалуйста, избегайте приведения. В C++ есть определенная роскошь, связанная с типами. Это позволяет избежать возможных ошибок. Кроме того, Индия пытается снимать слепки, так почему бы и вам? Это самая большая демократия на планете.   -  person Ed Heal    schedule 21.09.2013
comment
Это преобразование в void* очень полезно в условиях, когда необходимо проверить указатели на два разных интерфейса, чтобы узнать, действительно ли они принадлежат одному и тому же объекту. Особенно, когда имя наиболее производного класса, реализующего эти интерфейсы, неизвестно.   -  person nanda    schedule 21.09.2013
comment
@nanda - Нет - Если вы не знаете название интерфейса, не занимайтесь написанием программного обеспечения.   -  person Ed Heal    schedule 21.09.2013


Ответы (7)


Интересно, есть ли какие-либо подводные камни/проблемы при этом?

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

Можно ли каким-либо образом столкнуться с ошибкой или неопределенным условием при приведении к void* и обратно?

Да, сценарий, упомянутый в #1.

Как мы должны решать такие проблемы, если они есть?

Избегайте полного использования void * в C++, вместо этого используйте шаблоны и наследование.
В C он может вам абсолютно понадобиться в определенных ситуациях, но постарайтесь свести его использование к минимуму.
Итог:
C/C++ позволяет вам стреляй себе в ногу, тебе решать делать или не делать.

person Alok Save    schedule 05.09.2012

Это совершенно справедливо. Вот что стандарт говорит об этом:

§4.10 Преобразование указателя

2 Значение r типа «указатель на cv T», где T — тип объекта, может быть преобразовано в значение r типа «указатель на cv void». Результат преобразования «указателя на cv T» в «указатель на cv void» указывает на начало места хранения, где находится объект типа T, как если объект является наиболее производным объектом (1.8) типа T (то есть не является подобъектом базового класса).

что означает, что вы можете преобразовать указатель на класс в указатель void. А также ...

§5.2.9 Статическое приведение

10 Rvalue типа "указатель на cv void" может быть явно преобразовано в указатель на объектный тип. Значение типа указатель на объект, преобразованное в «указатель на cv void» и обратно в исходный тип указателя, будет иметь исходное значение.

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

Надеюсь, поможет. Удачи!

person Community    schedule 21.09.2013
comment
Первая цитата говорит, кстати, что преобразование из типа указателя объекта в void* может быть неявным (не требует приведения). - person dyp; 21.09.2013
comment
Какой стандарт/версию вы цитируете? Насколько я понимаю, это не C++03 и не C++11. - person dyp; 21.09.2013

В C++ вам не нужно статическое приведение, чтобы добраться до to void*:

int main()
{
    CFoo* foo = new CFoo;
    void* dt = foo;
    tfunc(dt); // or tfunc(foo);

    return 0;
}

NB: ваша реализация tfunc() вполне правильная в том смысле, что она действительно нуждается в приведении.

person quamrana    schedule 11.09.2014

Я нечасто видел приведение к void* в C++. Это практика в C, которой активно избегают в C++.

Приведение к void* удаляет все типы безопасности.

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

Опасность заключается в том, что вы можете привести void* к неправильному типу, так как вы больше не уверены в правильности типа.

person Drew Dormann    schedule 05.09.2012

Единственное, что предоставляет стандарт, это то, что с учетом 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 не обязательно правильно указывает на объект A. Правильный путь pA = static_cast<A*>(pB);
  • Если вышеуказанные пункты могут работать с большинством реализаций одиночного наследования, они никогда не будут работать с множественным наследованием для баз, отличных от первого: рассмотрим class A: public Z, public B { ... };, если Z не пусто, учитывая A, подкомпонент B не будет иметь тот же адрес A. (и множественное наследование в С++ везде, где есть iostream)
  • Иногда все зависит и от платформы: (char*)(void*)pI (где pI указывает на целое число) не будет таким же, как "*pI, если *pI в (-128..+127)" (это будет только на машинах с прямым порядком байтов)

В целом не думайте, что преобразование между типами работает, просто изменяя способ интерпретации адреса.

person Emilio Garavaglia    schedule 05.09.2012

Я знаю много функций в драйвере и т. д., использующих указатели void для возврата данных вызывающей стороне, схема в основном такая же:

int requestSomeData(int kindOfData, void * buffer, int bufferSize);

Эта функция может принимать различные типы данных в качестве параметра. Что они делают, так это используют bufferSize в качестве параметра, чтобы избежать записи в места памяти, в которые они не должны писать. Если bufferSize не соответствует или меньше, чем данные, которые должны быть возвращены, функция вместо этого вернет код ошибки.

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

person Nippey    schedule 05.09.2012

Это действительно?

Да, это действительно согласно стандарту § 5.2.9.7.

Значение prvalue типа «указатель на cv1 void» может быть преобразовано в значение prvalue типа «указатель на cv2 T», где T — тип объекта, а cv2 — такое же или большее значение cv-квалификации, чем cv1. Значение нулевого указателя преобразуется в значение нулевого указателя целевого типа. Значение типа указатель на объект, преобразованное в «указатель на cv void» и обратно, возможно, с другой квалификацией cv, должно иметь исходное значение. [Пример:

T* p1 = new T;
const T* p2 = static_cast<const T*>(static_cast<void*>(p1));
bool b = p1 == p2; // b will have the value true.
person billz    schedule 21.09.2013