Вчера я прочитал что ты написал и планировал самостоятельно
поэкспериментировать. Слова это одно, а когда начинаешь разбираться
сам легко может быть другое. Не вышло, нет времени, поэтому все что
я здесь пишу основано на исходных данных этой темы. Кстати беглое
изучение документации на тему memcpy показывает что существует
большое пространство для маневра не только с тривиальными ключами
компилятора, прагмами и атрибутами но и что важно, с подключаемыми
либами/выбором obj файлов, коих на самом деле для memcpy несколько.
Затем, я пытался выслушать тебя, потому что C, в особенности C++ это такое большое западло, что даже если ты используешь его много лет, все равно можешь узнать новые и шокирующие вещи. Опыт подобного есть я думаю у каждого, так что торопиться с выводами никогда не надо.
Однако новых подробностей ты не раскрыл. Твоя финальная мысль звучит так: берется указатель на int'а находящийся в упакованной структуре и уже одно это является UB.
И всё на этом. Даже не доходя до самой memcpy. Дальше, якобы, можно даже не двигаться.
Свои соображение по поводу UB ты аргументируешь пунктом ISO/IEC 9899:201x 6.3.2.3 Pointers пункт 7:
"A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned 68) for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer."
Я уже писал выше что 6.3.2.3.7 словесно совпадает и рассказывает о pointers alignment.
Однако смысл его другой.
Он описывает случай, когда существовал типизированный указатель. При обращении к этому указателю средствами языка с учетом типа всё было нормально. Этот указатель "конвертируется" т.е. при неизменном значении у него меняется тип. Смена типа приводит к новой интерпретации данных, что влечет за собой новый метод доступа. (Либо иной вариант кода, либо иную инструкцию итд). При этом новый метод доступа/инструкция CPU может ужесточить требования выравнивания к адресу. Дальнейшая попытка обращения средствами языка к данному типу данных приведет к UB. Вот именно об этом пункт 6.3.2.3.7 и предупреждает.
И ТОЛЬКО об этом. Пункт 6.3.2.3.7 не о том, что взятие указателя невыровненного int'а это сразу же автоматически UB. Ко всему прочему каста при этом нет.
Операция взятия указателя (адреса) на невыровненные данные совершенно валидна в языке C, никакого UB именно при взятии и дальнейшем существовании такого указателя нет.
Далее.
Нельзя было останавливаться на месте взятия указателя и заявлять что всё, стоп, дальше только UB.
Дальше указатель кастится к void* и это совершенно валидно. "UB так не работает". Даже (если такое предположить) что был UB, его можно дальнейшими действиями снять, прерватить не в UB. И рассматривать нужно ВСЁ что происходит, а не спотыкаться на первом же попавшемся. Так вот дальше там был каст к void* со всеми вытекающими.
Кроме того, можно привести такой аргумент, что на PC x86/x64 ситуация Гудвина (взятия невыровненного адреса поля упакованной структуры) совершенно валидна, никто никогда не считал её UB или как-то нелигитимной, "хаком" итд.
Но как может быть язык C на одной платформе (x86/64) совершенно правильным и легитимным, без UB, и в то же время тот же C на другой платформе (STM32) якобы UB?
Никак. Так в чем же дело?
А дело в том, что в этой ситуации не надо рассматривать ЯЗЫК. Си тут вообще не причем.
Сама изначальная ориентация на язык, типы, итд была неверна.
Соответственно, брать стандарт и начинать что-то выяснять изначально неверно.
Проблема находится полностью вне языка и лежит на уровне платформы.
Значения выравнивания адресов по умолчанию определяются (и имеют значение)
для кодогенератора и для конкретного прцессора. Язык о них ничего не знает.
Соответствено, и проблему следует искать не в языке/стандарте, а ТОЛЬКО на уровне
кодогенератора/оптимизатора или сопутствующих им кода/библиотек поддержки.
Таким образом про стандарт можно забыть, смысла упоминать его тут нет.
Упомяну с выравниванием также курьезный случай.
В некоторых, редких случаях на одном процессоре один и тот же бинарный код
приводил к исключению невыровненных данных (исключение шины).
Но запуск на чуть более младшей модели процессора при том что архитектура одна, семейство одно, приводил к тому что программа работала.
То же самое было когда код работающий на эмуляторе падал на реальном железе.
Едем дальше.
Попытаюсь высказать версию что это такое было (повторюсь, я не изучил проблему сам,
поэтому это лишь версия).
Все что ты писал о оптимизации верно, и трабла восходит к оптимизации
и дилемме либо копировать memcpy побайтно, либо попытаться оптимизировать.
Я считаю действительно, товарищи писавшие оптимальный выбор memcpy смотрят на тип указателя в оригинале (несмотря на то что он передается как void*)
И увидев что это изначально был int* выбирают оптимизированную версию memcpy.
А дальше - скорее всего, как всегда, надо читать lowlevel документацию.
(Чего тут, разумеется, никто не сделал)
Полагаю что есть либо атрибут, либо ключик, либо выбор варианта либы (объектника) memcpy, который анализирует адрес указателя и видя что он невыровенный копирует первые 1-3 байта побайтно (как и делает большинство memcpy на PC)
Embedded разработка это не "универсальные решения", тут многое кастомизируется.
Вот и всё.
Но никакого UB.
Теперь докучи, раз и так уже написал стену текста, впишу что не ответил ранее.
Странно что ты не знаешь или не узнаешь типы u32,s32,u16,s16,u8,s8 итд, например f32,f64. Это типы Linux Kernel. Кроме того, эти typedef'ы встречаются в разнообразных легендарных либах/sdk. Я встречал у Silicon Graphics, Nintendo, Sony.
Осталось только заметить, что написано это не в поиске чеьей-то "правоты" и не "правоты". На это плевать.
Написано это в поиске истины.