Казалось бы, для восстановления значения из аргумента функции void * всегда требуется метод 2, так почему же метод 1 (кажется) работает с типами char и int? Это неопределенное поведение?
Поскольку C специально разрешает преобразования между целыми числами и указателями. Это разрешено, поскольку может потребоваться express абсолютных адресов в виде целых чисел, особенно в программировании, связанном с аппаратным обеспечением. Результат может быть хорошим или может вызывать неопределенное поведение, подробности см. Ниже.
Когда вам нужно конвертировать между указателями и целыми числами, вы должны всегда использовать uintptr_t
вместо этого для четко определенных и переносимых преобразований. Этот тип изначально не был частью C, поэтому преобразования в другие целочисленные типы все еще разрешены.
Если метод 1 работает для char и int, почему он также не работает с хотя бы тип с плавающей точкой? Это не потому, что их размеры разные, то есть: sizeof (float) == sizeof (int) == sizeof (int *) == sizeof (float *). Это из-за строгого нарушения псевдонимов?
Поскольку типы с плавающей запятой не имеют специального случая, допускающего преобразование, как целочисленные типы. У них скорее есть явное правило, запрещающее приведение от указателей к плавающей точке. Поскольку такие преобразования не имеют никакого смысла.
Строгое алиасинг применяется только тогда, когда вы делаете "lvalue-доступ" к сохраненному значению. Вы делаете это, например, здесь: *(double *)a
. Вы получаете доступ к данным через тип (double
), совместимый с эффективным типом объекта (также double
), так что это нормально.
(double *)a
однако никогда не получает доступ к фактическим данным , но просто пытается преобразовать тип указателя во что-то еще. Строгий псевдоним не применяется.
Как правило, C допускает множество преобразований диких указателей, но у вас возникают проблемы только после того, как вы начнете де-ссылаться на данные через неверный тип. Именно тогда вы можете столкнуться с проблемами с невыразимым типом, смещением и строгим псевдонимом.
Подробности:
char c = 'P';
... char cVar1 = (char)a;
.
Преобразование из типа указателя в целочисленный тип. Результат не определен или определяется реализацией 1) . Доступ к указанным данным по lvalue отсутствует, строгое алиасинг не применяется 2) . char c = 'P';
... char cVar2 = *(char *)a;
.
Доступ по lvalue символа через символ указатель. Совершенно четко определены 3) . int d = 1024;
... int iVar1 = (int)a;
.
Преобразование из типа указателя в целочисленный тип. Результат не определен или определяется реализацией 1) . Доступ к указанным данным не происходит, строгое алиасинг не применяется 2) .
int d = 1024;
... int iVar2 = *(int *)a;
Lvalue доступ к int
через int
указатель. Совершенно четкое определение 3) .
float e = 14.5;
... float fVar1 = (float)a;
.
Преобразование из типа указателя в плавающее. Несовместимое преобразование типов, нарушение ограничения оператора приведения 4) .
float e = 14.5;
... float fVar2 = *(float *)a;
.
Доступ к значению float
через указатель float
. Совершенно четкие 3) .
double
... аналогично float
выше.
1) C17 6.3.2.3/6:
Любой тип указателя может быть преобразован в целочисленный тип. За исключением случаев, указанных ранее, результат определяется реализацией. Если результат не может быть представлен целочисленным типом, поведение не определено. Результат не обязательно должен находиться в диапазоне значений любого целочисленного типа.
2) C17 6.5 §6 и §7 . См. Что такое строгое правило псевдонимов?
3) C17 6.3.2.1 L-значения, массивы и обозначения функций и
C17 6.3.2.3/1:
Указатель на void может быть преобразован в или из указателя на любой тип объекта. Указатель на любой тип объекта может быть преобразован в указатель на void и обратно; результат должен сравниться с исходным указателем.
Кроме того, type
идеально подходит для оценки доступа через (квалифицированный) указатель к type
, C17 6.5 / 7 : «тип, совместимый с действующим типом объекта».
4) Ни одно из допустимых преобразований указателей, перечисленных в C17 6.3.2.3 . Нарушение ограничения C17 6.5.4 / 4 :
Тип указателя не должен быть преобразован в какой-либо плавающий тип. Плавающий тип не должен быть преобразован в любой тип указателя.