Это цитата из стандарта C11:
6.5 Выражения
...6 Эффективным типом объекта для доступа к его сохраненному значению является объявленный тип объекта, если таковой имеется. Если значение сохраняется в объекте, у которого нет объявленного типа, через lvalue, имеющий тип, который не является символьным типом, то тип lvalue становится эффективным типом объекта для этого доступа и для последующих доступов, которые не изменяют сохраненное значение. Если значение копируется в объект, не имеющий объявленного типа, с использованием
memcpy
илиmemmove
, или копируется как массив символьного типа, тогда эффективный тип измененного объекта для этого доступа и для последующих доступов, которые не изменяют значение, является действующий тип объекта, из которого копируется значение, если оно есть. Для всех других доступов к объекту, не имеющему объявленного типа, эффективный тип объекта - это просто тип lvalue, используемого для доступа.7 Объект должен иметь свое сохраненное значение, доступное только через выражение lvalue, которое имеет один из следующих типов:
- тип, совместимый с эффективным типом объекта,
- квалифицированная версия типа, совместимого с эффективным типом объекта,
- тип, который является типом со знаком или без знака, соответствующим действующему типу объекта,
- тип, который является типом со знаком или без знака, соответствующим квалифицированной версии действующего типа объекта,
- агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегированного или автономного объединения), или
- тип персонажа.
Означает ли это, что memcpy
нельзя использовать для обозначения типа таким образом:
double d = 1234.5678;
uint64_t bits;
memcpy(&bits, &d, sizeof bits);
printf("the representation of %g is %08"PRIX64"\n", d, bits);
Почему бы не дать такой же вывод, как:
union { double d; uint64_t i; } u;
u.d = 1234.5678;
printf("the representation of %g is %08"PRIX64"\n", d, u.i);
Что если я использую свою версию memcpy
с использованием типов символов:
void *my_memcpy(void *dst, const void *src, size_t n) {
unsigned char *d = dst;
const unsigned char *s = src;
for (size_t i = 0; i < n; i++) { d[i] = s[i]; }
return dst;
}
РЕДАКТИРОВАТЬ: EOF отметил, что часть о memcpy()
в параграфе 6 не применяется в этой ситуации, так uint64_t bits
имеют объявленный тип. Я согласен, но, к сожалению, это не помогает ответить на вопрос, можно ли использовать memcpy
для наказания типов, просто делает пункт 6 неуместным для оценки достоверности приведенных выше примеров.
Вот еще одна попытка типа Punning с memcpy
которая, я думаю, будет рассмотрена в пункте 6:
double d = 1234.5678;
void *p = malloc(sizeof(double));
if (p != NULL) {
uint64_t *pbits = memcpy(p, &d, sizeof(double));
uint64_t bits = *pbits;
printf("the representation of %g is %08"PRIX64"\n", d, bits);
}
Предполагая, что sizeof(double) == sizeof(uint64_t)
, имеет ли приведенный выше код определенное поведение в параграфах 6 и 7?
РЕДАКТИРОВАТЬ: Некоторые ответы указывают на потенциал неопределенного поведения, возникающего при чтении представления ловушки. Это не имеет значения, поскольку Стандарт C явно исключает эту возможность:
7.20.1.1 Целочисленные типы с точной шириной
1 Имя typedef
int
N_t
обозначает целочисленный тип со знаком с шириной N, без дополнительных битов и представление с дополнением до двух. Таким образом,int8_t
обозначает такой целочисленный тип соint8_t
шириной ровно 8 бит.2 Имя определения типа
uint
N_t
обозначает целочисленный тип без знака с шириной N и без битов заполнения. Таким образом,uint24_t
обозначает такой целочисленный тип без знака с шириной ровно 24 бита.Эти типы не являются обязательными. Однако, если реализация предоставляет целочисленные типы с шириной 8, 16, 32 или 64 бита, без битов заполнения и (для подписанных типов), которые имеют представление дополнения до двух, она должна определить соответствующие имена typedef.
Тип uint64_t
имеет ровно 64 значения битов и не имеет битов заполнения, поэтому не может быть никаких представлений прерываний.