memcpy из одного типа в другой. Как мы получим доступ к месту назначения после этого?

uint32_t u32 = 0;
uint16_t u16[2];
static_assert(sizeof(u32) == sizeof(u16), "");
memcpy(u16, &u32, sizeof(u32)); // defined?
// if defined, how to we access the data from here on?

Это определенное поведение? И если да, то какой тип указателя мы можем использовать для доступа к целевым данным после memcpy?

Должны ли мы использовать uint16_t*, потому что это подходит для объявленного типа u16?

Или мы должны использовать uint32_t*, потому что тип исходных данных (исходные данные, скопированные memcpy) uint_32?

(Лично интересует С++ 11/С++ 14. Но обсуждение родственных языков, таких как C, также было бы интересно.)


person Aaron McDaid    schedule 20.09.2016    source источник
comment
Теперь это так :-) (я забыл & и еще одну опечатку)   -  person Aaron McDaid    schedule 20.09.2016
comment
Вы пытаетесь узнать порядок байтов вашего процессора?   -  person Bob__    schedule 20.09.2016
comment
@Bob__, в данный момент я не имею в виду конкретное приложение. Но я скажу, что, в зависимости от ответа, это может позволить некоторые агрессивные оптимизации, которые я мог бы рассмотреть в будущем в определенных проектах.   -  person Aaron McDaid    schedule 20.09.2016
comment
@Bob__, может быть, это лучше передает то, что я надеюсь подтвердить: данная переменная, объявленная как имеющая тип X, будет всегда обращаться к ней через указатель типа X. И memcpy в такую ​​переменную всегда круто и не изменит тип, пока вы придерживаетесь простых типов, где вы можете рассуждать о битовых шаблонах (целые числа, указатели, ...) и не иметь проблем с ловушка представления или выравнивание.   -  person Aaron McDaid    schedule 20.09.2016


Ответы (2)


Стандарт C++ делегирует стандарту C:

Содержимое и значение заголовка <cstring> совпадают с заголовком <string.h> стандартной библиотеки C.

Стандарт C определяет:

7.24.1/3 Для всех функций в этом подпункте каждый символ должен интерпретироваться так, как если бы он имел тип unsigned char (и, следовательно, все возможные представления объекта допустимы и имеют другое значение).

Итак, чтобы ответить на ваш вопрос: да, поведение определено.

Да, uint16_t* подходит, потому что uint16_t — это тип объекта.


Нет, тип источника не имеет значения.

Стандарт С++ не определяет такую ​​вещь, как объект без объявленного типа или то, как он будет себя вести. Я интерпретирую это как означающее, что эффективный тип — это реализация, определенная для объектов без объявленного типа.

Даже в C источник в этом случае не имеет значения. Более полная версия цитаты из стандарта C (черновик, N1570), которая вас беспокоит, выделено мной:

6.5/6 [...] Если значение копируется в объект, не имеющий объявленного типа с помощью memcpy или memmove, или копируется как массив символьного типа, то эффективный тип измененного объекта для этого доступа и для последующих доступов, которые не изменяют значение, является эффективным типом объекта, из которого копируется значение, если он есть. [...]

Это правило не применяется, поскольку объекты в u16 имеют объявленный тип.

person eerorika    schedule 20.09.2016
comment
Небольшое дополнение: «объявленный тип» объекта, если он есть, никогда не может измениться? И объект никогда не может потерять или получить «объявленный тип»? Но может ли «эффективный тип» меняться при определенных обстоятельствах? - person Aaron McDaid; 20.09.2016
comment
Объявленный @AaronMcDaid тип не меняется. Если объект имеет объявленный тип, то он также является эффективным типом и не меняется. Эффективный тип можно получить или изменить, если нет объявленного типа. - person eerorika; 20.09.2016
comment
Теперь я думаю, что огромная часть ответов со строгим псевдонимом на другие вопросы о переполнении стека имела бы гораздо больше смысла, если бы эти комментарии здесь о «объявленном» и «эффективном» типе предварялись в начале каждого ответа. - person Aaron McDaid; 20.09.2016
comment
@AaronMcDaid: я не знаю определений для объявленного типа и объекта, которые бы соответствовали всем существующим поведениям компилятора. Если функция получает указатель типа foo*, компиляторы могут рассматривать ее цель так, как если бы она имела объявленный тип foo, независимо от того, действительно это так или нет. - person supercat; 22.09.2016

Является ли это определенным поведением?

Да. memcpyВключение в модуль четко определено, и вы обеспечили правильный размер.

Должны ли мы использовать uint16_t*, потому что это подходит для объявленного типа u16?

Да, конечно. u16 — это массив из двух uint16_t, поэтому к нему нужно обращаться как таковому. Доступ к нему через uint32_t* будет неопределённым поведением по правилу строгого псевдонима.

Неважно, какой был тип источника. Важно то, что у вас есть объект типа uint16_t[2].


С другой стороны, это:

uint32_t p;
new (&p) uint16_t(42);
std::cout << p;

является неопределенным поведением, потому что теперь есть объект другого типа, время жизни которого начинается с &p, и мы обращаемся к нему через неправильный тип.

person Barry    schedule 20.09.2016
comment
Одна вещь, которая меня беспокоит, это то, что в стандарте C есть предложения, которые предполагают, что тип uint32_t, например [тип становится ] ... эффективным типом объекта, из которого копируется значение ... . У меня нет никаких документов по стандартам, все, что я могу сделать, это скопировать и вставить материал, который я нахожу в Интернете - я нашел это предложение здесь: stackoverflow.com/questions/30970251/ - person Aaron McDaid; 20.09.2016
comment
@AaronMcDaid C++ — это не C. - person Barry; 20.09.2016
comment
За исключением случаев, когда C++ явно указывает, что он является C, в некоторых контекстах. (См. ответ от пользователя 2079303). - person Aaron McDaid; 20.09.2016
comment
Есть ли у этого преимущество перед reinterpret_cast? И то, и другое должно привести к определенным результатам реализации, я думаю. - person Bob__; 20.09.2016
comment
@AaronMcDaid Я просто повторю, что C ++ - это не C. memcpy не начинается время жизни объекта, а только получение хранилища и (при необходимости) вызов конструктора. Ваш массив uint16_t содержит два uint16_t. Там нет объекта типа uint32_t. - person Barry; 20.09.2016
comment
@Bob__ reinterpret_cast - UB. - person Barry; 20.09.2016
comment
@AaronMcDaid Я должен уточнить, что 6.5/6 из стандарта C, который вы и я цитируем, не применяются к C ++. 7.24 и несколько других явно указанных глав применимы к C++. - person eerorika; 20.09.2016