Может быть. Стандарт C гарантирует только следующее, 6.3.2.3:
Указатель на void
может быть преобразован в или из указателя на любой тип объекта. Указатель на
любой тип объекта может быть преобразован в указатель на void
и обратно; результат должен
сравнить, равный исходному указателю.
Теоретически / формально, void*
не обязательно должен иметь то же представление памяти, что и указатель на объект. Всякий раз, когда он сталкивается с таким преобразованием, он теоретически может делать некоторые трюки, такие как отслеживание исходного значения, чтобы восстановить его позже.
На практике void*
и указатели на объекты имеют одинаковый размер и формат в любой здравомыслящей системе, потому что это самый простой способ удовлетворить вышеуказанное требование. Существуют системы с расширенными режимами адресации для объектов, но они не работают так, как предполагал стандарт C. Вместо этого они используют нестандартные расширения, изобретая квалификаторы указателей near
и far
, которые затем можно применить либо к void*
, либо к указателю объекта. (Некоторые примеры этого - микроконтроллеры низкого уровня и старая MS DOS.)
Итог: проектирование для переносимости в безумные или вымышленные системы - огромная трата времени. Вбросить static_assert(sizeof(void*) == sizeof(int*), "...")
и этого должно быть достаточно. Потому что на практике вы не найдете систему с магическим или загадочным форматом void*
. Если да, то разберись с этим.
Однако вы должны иметь в виду, что void*
сам по себе является другим типом, поэтому вы обычно не можете получить доступ к памяти, в которой void*
хранится через другой тип указателя (строгое указание псевдонимов). Но есть несколько исключений из строгого правила псевдонимов, одним из них является наказание типов через профсоюзы.
Итак, что касается вашего union
, void*
и первый указатель объекта гарантированно будут размещены, начиная с одного и того же адреса, но опять-таки их соответствующие размеры и внутренние форматы теоретически могут быть разными. На практике вы можете предположить, что они одинаковы, и поскольку вы используете union
, вы не нарушаете строгий псевдоним.
Что касается лучшей практики, то это, во-первых, избегать void*
, так как там очень мало мест, где они вам действительно нужны. В тех немногих случаях, когда вам нужно передать какой-то общий указатель, часто лучше использовать uintptr_t
и сохранять результат как целое число.