Говнокода. И не такие уж и тысячи. Практически всё что завелось не
на x86 такого говнокода не содержит. Потому, что и MIPS, и ARM --
это аппаратное исключение при невыравненном обращении и дальше либо
фиксация ошибки, либо программная эмуляция команды с невыравненным
чтением-записью (очень не быстро...) И даже на современном x86
словить исключение при невыравненном обращении -- запросто
(векторные инструкции). Ещё раз, повторю, упакованные структуры --
НЕ СТАНДАРТНАЯ ХЕРНЯ. Никто и не обещал что она может работать во всех возможных случаях, тем более кроссплатформенно. На самом деле случаи когда она работает исключительно узкие: когда ты непосредственно обращаешься к полям структуры руками. ВСЁ. Это такой хакерский способ сериализовать данные. Работать полноценно с таким контейнером -- невозможно, перекладывай в нормальную структуру. Только и (де)сериализовать совсем уж руками тоже не сложно.
Разумеется компилятор оптимизирует код. Работай он как в книгах написано, ты бы не получал такой скорости в первую очередь. Тут уже писали: printf заменяет на puts когда может, чтения по байтику заменяет на чтение словами (так в 4-8 раз быстрей!), разворачивает циклы, переставляет местами операции, да там много чего, и достаточно глубоко -- сам не придумаешь сходу. И разумеется все преобразования кода осуществляются так, чтоб код был эквиэвалентен, давал такой же результат, в той модели вычислительной машины в которой работает компилятор, а он не оперирует битами и байтами конкретной модели процессора, у него своя более высокоуровневая модель определённая стандартом языка. И в ней совершенно естественно, что если переменная имеет тип "указатель на int", то содержимое памяти можно прочитать как одно машинное слово целиком за одну операцию, а не как 4 отдельных байтика за 4 операции. Так же совершенно естественно не писать цикл, как у тебя в самодельном memcpy сделано было, а поскольку он заранее знает сколько байт копируется, то может сразу сгенерировать код копирующий N байт, и даже вместо call куда-то-там просто заинлайнить этот код в твою функцию в несколько машинных инструкций. Но ты ему под видом указателя на int подсунул нечто совершенно другое, другой тип данных, тип с alignas(1) вместо alignas(4). И компилятору тебе даже варнинг не показать, т.к. тип он сам знает (неправильный) и с его точки зрения всё ок. Потому, что вот этот функционал, упакованные структуры -- кривая недоделка. Типы он не изменяет и новые не вводит, но обращения к переменным по тем типам, с которыми они декларированы -- тоже невозможно.
Хочешь использовать упакованные структуры -- работай с ними совсем вручную. Но толку от того никакого не будет. У тебя будет медленный и неэффективный memcpy, медленное и неэффективное обращение к каждому элементу. И ради чего? Ради того, чтоб однажды её скопировать куда-то целиком, без "лишних" нулей? Причём медленной версией memcpy. Не проще ли работать с нормальной структурой, и только когда её нужно единожды скопировать (передать по каналу связи) -- "сжать" её, выкинувь лишние нули при сериализации? Тем более, что при этом можно задать нужную ширину каждого поля. А в самой структуре не пытаться делать 8 или 16-битные поля, так как 32-битный процессор с такими полями работает не эффективно, путём применения нескольких лишних инструкций на каждый чих.
Все твои "ранние версии" прекрасно работали на 8-битных процессорах и x86, на котором тянули лямку совместимости с говнокодом. Но время 8-битных процессоров и x86 уже ушло безвозвратно. В современном мире нужны несколько другие подходы.
PS: пример кода сериализации структуры: http://caxapa.ru/993200/