LINUX.ORG.RU

Как выравнивать данные?


0

3

Современные процессоры не такие же, как те процессоры, на которых разрабатывали язык Си. В новых процессорах огромные кеши, которые состоят из «строк», и данные в них пересылаются целиковыми строками (это моя гипотеза), по крайней мере уж очищаются-то точно строками. «This enables several tricks, like making sure neighboring elements of an array never share the same cache line with each other (which may speed up certain kinds of concurrent code).»
Так или иначе, для данных сегодня очень полезно быть выровненными на границу 8 байт (при 64-х битной архитектуре вроде x86_64), или даже на границу 16-ти байт (стек вроде так просят выравнивать). «The choice of both Windows x64 and x86-64 System V to maintain 16-byte stack alignment». С мотивацией, что «некоторые типы данных, такие как SSE- и AVX-векторы, требуют выравнивания по границе 16 байт для корректной работы».

В спецификации языка Си возможность выравнивания типов на границу появилась начиная с C11.

«alignment of any given struct or union type is required by the ISO C standard to be at least a perfect multiple of the lowest common multiple of the alignments of all of the members of the struct or union in question.»

Мне надо, чтобы экземпляры структуры всегда выравнивались на границу 16 байт (в стеке и в куче). Как мне этого добиваться?

Пункт 7.15 стандарта C11:

7.15 Alignment <stdalign.h>
1 The header <stdalign.h> defines four macros.
2 The macro
alignas
expands to _Alignas; the macro
alignof
expands to _Alignof.
3 The remaining macros are suitable for use in #if preprocessing directives. They are
_ _alignas_is_defined
and
_ _alignof_is_defined
which both expand to the integer constant 1.

В C11, вы можете использовать ключевое слово alignas для установки выравнивания структуры.

alignas(16) struct MyStruct {
    // поля структуры
};

«Для выделения памяти для таких типов данных следует использовать функции aligned_alloc и подобные». И тут начинаются разброд и шатание:

  • C11

    void * aligned_alloc (size_t alignment, size_t size)

  • POSIX

    int posix_memalign (void **memptr, size_t alignment, size_t size)

В GCC, для выравнивания структур и объединений, можно использовать атрибут __attribute__((aligned(n))). Но непонятно, требование по выравниванию применятся к самой структуре в целом, или к каждому полю в ней (думал, что второе, а на самом деле оказалось, что первое).

To specify multiple attributes, separate them by commas within the double parentheses: for example, __attribute__ ((aligned (16), packed)). Ну ок, а в C11 как добится такого же эффекта (выравнивание + packed)?

В LLVM реализация стандарта C11 тоже должна быть.

Ещё есть пункт 7.19 с типом max_align_t

7.19 Common definitions <stddef.h>
1 The header <stddef.h> defines the following macros and declares the following types.
Some are also defined in other headers, as noted in their respective subclauses.
2 The types are
...
max_align_t
which is an object type whose alignment is as great as is supported by the implementation in all contexts;

Отсюда, насколько я понимаю, если мне надо выравнивание не менее чем на 16 байтов, использовать malloc в коде нельзя (только aligned_alloc, возможно с alignof).

Я это всё потому пишу, что наткнулся на github-е на фрагмент кода, и мне захотелось сделать по-аналогии, только выравнивать не на 4 байта, а на 16. Наличие четырёх битов позволило бы мне в них закодировать не только разницу в глубине/высоте поддеревьев, но и количество дочерних узлов (два, один, или ни одного). Зная, что в листе нет дочерних, можно выделять под него меньше памяти, а листов в дереве чуть ли не половина от всех узлов.

Стоит ли заморачиваться именно с такой структурой дерева (для экономии памяти), если всё равно результат будут размещать в докере и потеряют много ресурсов в других местах, а предварительная оптимизация - зло? Может быть уже кто-то готовый код написал, никто не видел?

А что, если бы это был не Си, а какой-нибудь Rust?

«Один из способов управления выравниванием в Rust - использование атрибута #[repr(C)]. Этот атрибут гарантирует, что структура данных будет представлена в памяти так, как если бы она была написана на C. Это означает, что порядок и размер полей структуры будут соответствовать тому, что определено в C, и выравнивание будет соответствовать стандартам C»

«использование атрибута #[repr(align(16))] может привести к неэффективному использованию памяти, так как компилятор будет добавлять дополнительные заполнения в структуру, чтобы удовлетворить требование выравнивания.»

★★★

Последнее исправление: Shushundr (всего исправлений: 1)

Стоит ли заморачиваться именно с такой структурой дерева

Я бы на твоем месте сначала протестировал два варианта на твоем юз кейсе: с выравниванием и без. Если прирост будет пол процента, то эта возня не стОит того.

Где-то слыхал что структуры и так автоматом выравниваются алокатором памяти. Но это не точно)

iron ★★★★★
()

Вот понаписал то.

Но непонятно, требование по выравниванию применятся к самой структуре в целом, или к каждому полю в ней (думал, что второе, а на самом деле оказалось, что первое).

Почему непонятно и при чём тут вообще структура? Это атрибут типа переменной. Тип может быть и структурой но это совершенно ничего не меняет. Внутрь неё соответственно оно лезть не будет.

To specify multiple attributes, separate them by commas within the double parentheses: for example, __attribute__ ((aligned (16), packed)). Ну ок, а в C11 как добится такого же эффекта (выравнивание + packed)?

Никак, в C11 нет packed. Можешь попробовать на каждое поле ставить alignas(1) но не факт что оно позволяет уменьшать выравнивание от дефолтного. А ещё - просто используй возможности gcc и забей на странные несовместимые с ним компиляторы.

Отсюда, насколько я понимаю, если мне надо выравнивание не менее чем на 16 байтов, использовать malloc в коде нельзя (только aligned_alloc, возможно с alignof).

Логика непонятна. max_align_t и malloc это совершенно не связанные друг с другом вещи. malloc и alignof - тоже. malloc просто выделяет память где получится, у него всего один аргумент - нужное количество байт, а на синтаксические конструкции описаний типов ему пофиг. Считай что он выравнивает до 1 байта, большее никем не гарантируется, хоть обычно и имеет место.

Стоит ли заморачиваться

Если хочешь оптимизировать память то начать стоит со своего кастомизированного аллокатора. Заодно и выравнивания реализуешь.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Считай что он выравнивает до 1 байта, большее никем не гарантируется, хоть обычно и имеет место.

IRL на 64-битных системах везде по 8 байтам выровнено, причем на это даже закладываются рантаймы некоторых ЯП.

lovesan ★★
()

Отсюда, насколько я понимаю, если мне надо выравнивание не менее чем на 16 байтов, использовать malloc в коде нельзя (только aligned_alloc, возможно с alignof).

Что тебе мешает выделить память malloc и потом просто найти ближайший выровненный адрес в выделенной памяти, и плясать уже от него?

lovesan ★★
()
Ответ на: комментарий от firkax

начать стоит со своего кастомизированного аллокатора

«since 2018, Rust programmers have been able to set a custom global allocator».

Это, конечно прикольно (наверное, можно вместо указателей использовать индексы меньшей битности, например), но превышает возможности, доступные на моём уровне интеллекта.

https://www.brochweb.com/blog/post/how-to-create-a-custom-memory-allocator-in-rust/

Shushundr ★★★
() автор топика
Последнее исправление: Shushundr (всего исправлений: 1)
Ответ на: комментарий от firkax

Считай что он выравнивает до 1 байта, большее никем не гарантируется

Это гарантируется в стандарте языка.

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

7.20.3 Memory management functions

[…] The pointer returned if the allocation succeeds is suitably aligned so that it may be assigned to a pointer to any type of object and then used to access such an object or an array of such objects in the space allocated (until the space is explicitly deallocated).

cudeta
()
Ответ на: комментарий от cudeta

И к чему ты это притащил? Во-первых оно не противоречит тому что я написал, а во-вторых надо смотреть не позднее чем C89 - всё более позднее по сути опционально по совокупности причин и следовательно надеяться на него в общем случае нельзя.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 2)
Ответ на: комментарий от firkax

Да, я так и думал, что ты не поймёшь.

Во-первых оно не противоречит тому что я написал

Противоречит. Стандарт вообще запрещает существование unaligned указателей, то есть, указателей на T, не выровненных на alignof(T).

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

То есть, по сути, там написано, что вот такой код не приводит к неопределённому поведению для любого Type:

Type *p = malloc(sizeof(Type));

всё более позднее по сути опционально по совокупности причин и следовательно надеяться на него в общем случае нельзя.

В C89 то же самое :)

https://web.archive.org/web/20200909074736if_/https://www.pdf-archive.com/201...

7.10.3 Memory management functions

The pointer returned if the allocation succeeds is suitably
aligned so that it may be assigned to a pointer to any type of object and then used to access such
an object or an array of such objects in the space allocated (until the space is explicitly freed or
reallocated).

Сейчас ты начнёшь кричать, что стандарт тебе не стандарт, но вот простой пруф:


$ cat foo.c
#include <stdlib.h>

int main()
{
    int *p = malloc(sizeof(int) * 2);
    if (!p)
        abort();
    char *q = (char *) p;
    *((int *) (q + 1)) = 0;
}
$ gcc -fsanitize=undefined foo.c
$ ./a.out
foo.c:9:24: runtime error: store to misaligned address 0x559d47cb42b1 for type 'int', which requires 4 byte alignment
0x559d47cb42b1: note: pointer points here
 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  51 00 00 00 00
              ^ 
$ clang-14 -fsanitize=undefined foo.c
$ ./a.out
foo.c:9:5: runtime error: store to misaligned address 0x55a60d4062e1 for type 'int', which requires 4 byte alignment
0x55a60d4062e1: note: pointer points here
 00 00 00  06 d4 60 5a 05 00 00 00  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  71 00 00 00 00
              ^ 
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior foo.c:9:5 in 

cudeta
()
Ответ на: комментарий от cudeta

Стандарт вообще запрещает существование unaligned указателей, то есть, указателей на T, не выровненных на alignof(T).

Где ты такое прочёл? В той цитате про это не было, если что.

То есть, по сути, там написано, что вот такой код не приводит к неопределённому поведению для любого Type:

Не приводит. Только причём тут выравнивание? Обращение по неправильно выровненному адресу может быть проблемным для некоторых архитектур, но не более того. В частности, для x86 таких проблем нет. Фразу стоит переводить так: на архитектурах, где проц не умеет обращаться по невыровненному адресу слова, malloc будет возвращать только выровненные указатели. Кстати, сюда можно наверно приписать некоторые sse, но если -msse или 64-бит не указаны то нельзя.

В C89 то же самое :)

Я и не сомневался.

Сейчас ты начнёшь кричать, что стандарт тебе не стандарт, но вот простой пруф:

Это параноидальные настройки проверок.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Да уж, сишники, которые не знаю стандарта, это классика.

Где ты такое прочёл?

В стандарте, чувак!

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

6.3.2.3 Pointers

7 A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned57) for the pointed-to type, the behavior is undefined.

J.2 Undefined behavior

— Conversion between two pointer types produces a result that is incorrectly aligned (6.3.2.3).

Обращение по неправильно выровненному адресу может быть проблемным для некоторых архитектур, но не более того.

Нет, по стандарту, само существование таких указателей это UB.

Обращение по неправильно выровненному адресу может быть проблемным для некоторых архитектур, но не более того.

Ты ошибаешься.

Кстати, сюда можно приписать sse всякие, но если -msse не указано то нельзя.

Причём тут SSE? Как мне в стандартном C99 или C89 получить тип, у которого выравнивание больше 8?

P.S. Ты там в каком году застрял? -msse это умолчание на x86-64, SSE2 это часть описания архитектуры и SysV ABI.

Это параноидальные настройки проверок.

А почему эти «параноидальные настройки» включаются при -fsanitize=undefined? Выходит, что и GCC, и Clang считают, что это UB?

cudeta
()
Ответ на: комментарий от cudeta

В стандарте, чувак!

Повторю: для x86 не бывает никаких incorrect alignment, проц умеет читать/писать по любому адресу (про sse см. ниже). То что про это написано в стандарте - это на случай архитектур которые так не умеют. В самом деле: ты написал код на Си с невыровненным доступом, всё прекрасно работает, даёшь его своему другу с какой-то ембеддед-архитектурой и у него оно падает с alignment trap или, ещё хуже, игнорирует младший бит адреса int16-переменной, читая её на 1 байт ниже чем она расположена по задумке программиста. Это и правда в общем случае UB - мы не можем заранее знать что там за проц будет итд. Но если речь идёт про компиляцию с уже известной x86 архитектурой, и линкуется оно к libc, написанному именно под x86, то невыравнивание указателей никакого UB не создаёт: libc знает что так можно, компилятор тоже знает, а программиста это вообще не задевает. А gcc-шный packed struct вообще получается UB by design тогда.

Но если хочешь, можешь интерпретировать это по-своему типа там страшное UB.

В любом случае, это ни на что не влияет, поскольку glibc malloc даже на 32 битах выравнивает указатели по 16 байт по другим причинам. А аллокатор 16-битного Турбо-Паскаля выравнивал по 8 (16-битный Си не видел).

Причём тут SSE?

При том что это единственная сущность в x86 процах, которая зависит от выравнивания. И то не везде, а на современных процах нет смысла генерить align-зависимый асм (на старых align-зависимая инструкция выполнялась чуть быстрее чем универсальная) так что считай уже тоже не зависит.

Как мне в стандартном C99 или C89 получить тип, у которого выравнивание больше 8?

Думаю никак.

P.S. Ты там в каком году застрял? -msse это умолчание на x86-64, SSE2 это часть описания архитектуры и SysV ABI.

Я и написал: "-msse или 64 бита". Т.к. второе включает в себя первое.

А почему эти «параноидальные настройки» включаются при -fsanitize=undefined? Выходит, что и GCC, и Clang считают, что это UB?

Не знаю. Формальности. У шланга так точно, они злостные фанаты стандартов и наверно скрипели зубами от злости когда приходилось gcc-расширения реализовывать (иначе б их компилятор ни для чего не годился).

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от firkax

Но если речь идёт про компиляцию с уже известной x86 архитектурой, и линкуется оно к libc, написанному именно под x86, то невыравнивание указателей никакого UB не создаёт

Тогда почему GCC оптимизирует этот код так, как будто указатели всегда выровненные (подумай, что будет, если они пересекаются, но не равны)?

int h(int *p, int *q){
  *p = 1;
  *q = 1;
  return *p;
}
   0:	c7 07 01 00 00 00    	movl   $0x1,(%rdi)
   6:	b8 01 00 00 00       	mov    $0x1,%eax
   b:	c7 06 01 00 00 00    	movl   $0x1,(%rsi)
  11:	c3                   	ret

Опять «не знаю, формальности»? Получается, что у тебя какое-то своё видение стандарта, с которым не согласны ни разработчики GCC, ни Clang?

cudeta
()

А не выравнивай, экономь лучше, как диды делали. Если программа в 64К влезет - будет абсолютно пофиг какое там у процессора выравнивание

DumLemming ★★
()

Эмм, столько рассуждений… Да и чем тебе не угодил malloc Если хочешь выровненный кусок, то бери больше на размер выравнивания и отреж от начала невыровненный кусок. Так например в сишном и ассемблерном коде нередкой была конструкция aligned_ptr = ptr & 0xFFFFFFF0(или что-то подобное; 32 бит адреса), что собственно и есть то самое выравнивание по 16 байт, переносимость не 100%(от модели памяти зависит), а в остальном ответ на Мне надо, чтобы экземпляры структуры всегда выравнивались на границу 16 байт (в стеке и в куче). Как мне этого добиваться? зависит только от твоего инструментария и принятого решения. В целом если тебе хочется выравнивать именно элементы по границе в 16 байт, то тебе нужно к выравниванию начала памяти дополнить размер структуры до размера выравнивания, желательно средствами компилятора для переносимости или строго следить за порядком полей структуры(причём тут много возможных вариантов) и их внутренним выравниванием. Кратко:

  1. выравнивание и упаковка решают разные задачи, как правило когда говорят о выравнивании, то говорят не об экономии, а о производительности, т.к. выравнивание решает проблему издержек при чтении разорванных данных в широких регистрах.
  2. Упаковка данных оправданатолько при одном условии - если вас не заботить возможная просадка по производительности из-за перерасхода тактов на восстановление данных, по сути нужно это только для согласования двух вещей:
    1. Места хранения
    2. Сжатие потока передачи Где под согласованием предполагается, что чтение с внешнего канала не требует постобработки данных для хранения(т.е. вы экономите на постобработки читая данные напрямую, но теряете затем в скорости при обработке данных). Причём все эти старания могут всё равно оказаться ни о чём, если пересылка данных происходит на машинах с разным порядком байт и всё равно тем самым вам придётся выполнять постобработку.

Короче - конечно хорошо о таких вещах рассуждать с оглядкой на производительность и пр., но по сути единственный стоящий перед тобой вопрос - есть ли в этом необходимость, вызванная требованиями или нет. Если требования по экстремальной экономии места или переноса данных по сети не возникало, то упаковка вам не нужна. А касательно выравнивания только отмечу, что желательно всё-таки поддерживать порядок полей в структуре так, чтобы не требовалось вставки пустот компилятором.

AKonia ★★
()
Ответ на: комментарий от cudeta

Компилятор предполагает, что эти указатели не пересекаются (т.е. либо равны, либо отличаются 4 или более. Как они выравнены - не важно. Один может быть равен 123, другой 129 и всё будет нормально.

vbr ★★★
()
Ответ на: комментарий от vbr

Компилятор предполагает, что эти указатели не пересекаются (т.е. либо равны, либо отличаются 4 или более.

Верно. Из «выровнены» следует это.

Как они выравнены - не важно.

Важно. Например, если я заменю тип на другой с выравниванием 1, то код будет другой:

typedef int my_int __attribute__((aligned(1)));

my_int h(my_int *p, my_int *q){
  *p = 1;
  *q = 1;
  return *p;
}
   0:	c7 07 01 00 00 00    	movl   $0x1,(%rdi)
   6:	c7 06 01 00 00 00    	movl   $0x1,(%rsi)
   c:	8b 07                	mov    (%rdi),%eax
   e:	c3                   	ret

Замена __attribute__((aligned(1))) на __attribute__((may_alias)) не вызывает такого изменения (почитай, что такое may_alias, оно про типы указателей вообще, но тут тип один и тот же).

Один может быть равен 123, другой 129 и всё будет нормально.

Я выше показал, что стандарт прямо запрещает существование таких указателей.

cudeta
()
Ответ на: комментарий от cudeta

Всё, что с attribute это компиляторо-специфичное и к языку отношения не имеет.

Я выше показал, что стандарт прямо запрещает существование таких указателей.

Я этого запрета не увидел. Стандарт требует, чтобы указатель был выровнен как положено. На x86 выравнивание по одному байту это нормально, так что никаких проблем с невыровненным по 4-байтовой границе int-ом не будет с точки зрения стандарта.

Другой вопрос, что выравнивание это самый простой способ обеспечить, чтобы данные не пересекались где не надо.

vbr ★★★
()
Последнее исправление: vbr (всего исправлений: 1)
Ответ на: комментарий от vbr

Всё, что с attribute это компиляторо-специфичное и к языку отношения не имеет.

Но мы говорим про GCC. Ты утверждал, что оптимизация не имеет отношения к выравниванию, а имеет отношение к “aliasing”. Но если сказать GCC, что выравнивания нету, то оптимизация исчезает, а если сказать may_alias — то нет.

На x86 выравнивание по одному байту это нормально, так что никаких проблем с невыровненным по 4-байтовой границе int-ом не будет с точки зрения стандарта.

Тогда почему оба компилятора ругаются при -fsanitize=undefined? Выходит, что и GCC, и Clang считают, что это UB?

cudeta
()
Ответ на: комментарий от cudeta

Релевантная дискуссия: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93031

Note that GCC aims to allow partial overlap for situations when alignment<size, see responses from Richi in PR 91091 («Note that GCC middle-end semantics would allow partial overlaps here unless you factor in alignment requirements») and PR 91419.

cudeta
()
Ответ на: комментарий от cudeta

Пример печален. Наконец-то кто-то мне показал как gcc беспричинно портит ход выполнения алгоритма при умеренных оптимизациях (-O1). Началось это в gcc 8, 7.5 ещё показывает хороший код. https://godbolt.org/z/o4bqhzfYz

Ну выходит да, gcc тоже начал тут делать UB. Одно радует, я вряд ли такой код напишу. Но надо быть осторожнее наверно а то вдруг где-то ещё подставу организуют.

firkax ★★★★★
()
Ответ на: комментарий от cudeta

По ссылке явный саботаж. В качестве оправданий там кроме всё того же sse ничего не указано, но 32-битный no-sse режим всё равно работает так же. Более того, им даже примеры потенциально ломающегося кода в т.ч. в ядре линукс привели, но им пофиг.

firkax ★★★★★
()
Последнее исправление: firkax (всего исправлений: 1)
Ответ на: комментарий от cudeta

Противоречит. Стандарт вообще запрещает существование unaligned указателей, то есть, указателей на T, не выровненных на alignof(T).

Особенно вот тут:

typedef struct T {
   char stuff[9000];
} T;

..

T* t = malloc(sizeof(T));

:)

Waterlaz ★★★★★
()

Стоит ли заморачиваться именно с такой структурой дерева (для экономии памяти)

Для экономии памяти точно не стоит.

Но если хочется заморочиться, то может надо посмотреть в сторону union и битовых полей?

blex ★★
()
Ответ на: комментарий от Shushundr

Я не знаю реальных размеров ваших деревьев, возможно не до конца понял проблему.

Если вы хотите действительно экономить память, подумайте о других идеях, например храните дерево в предварительно выделенном массиве и используйте индексы вместо указателей, индекс может быть в 4 байтах вместо 8 указателя (я говорю о 64 бита). Думаю еще много чего можно найти, и оно будет более эффективно, чем спрятать информацию в нижних байтах указателя.

blex ★★
()
Ответ на: комментарий от Shushundr

Без подробностей о коде и предметной области могу предложить, опять же, смотреть на битовые поля; смотреть на кастомный аллокатор, который позволит меньше тратить памяти на накладные расходы по поддержанию дерева; посмотреть с помощью pahole на выравнивание данных и закрыть дырки;

А вообще это больше к предметной области - проанализируйте что вы храните и в каких типах данных.

blex ★★
()
Ответ на: комментарий от blex

А вообще это больше к предметной области

Ага, например к какой предметной области относится реализация деревьев в библиотеке glib? Ни к какой, она там общего применения!

кастомный аллокатор

Уже советовали

битовые поля

уже советовали (это, как бы, и так очевидно)

посмотреть с помощью pahole на выравнивание данных и закрыть дырки

Вот такого ещё не советовали. Но что оно там увидит, если всё станет на битовых полях и макросах?

Shushundr ★★★
() автор топика

Так тоже можно. Часто при описании какого-нить функционального блока контроллера делают. Итого тут 24 байта будет. Если без #pragma pack, то каждое поле на int было б выровнено, т.е. 32 байта тут было б на 64-битном проце.

#include <inttypes.h>

#pragma pack(push)
#pragma pack(1)

struct mystruct
{
  uint32_t  a;
  uint32_t  b;
  union
  {
    uint8_t  c;
    uint64_t align_01;
  };
  uint64_t  d;
};

#pragma pack(pop)

При аллокации традиционно стараются выравнивать блоки на границу int (размер машинного слова). Для DMA наверное необходимо. В x86 чтение невыровненных данных ведет к необходимости читать соседние слова памяти и брать часть из одного и другого. При том, что данные лежат уже в быстрой памяти кэша, то это наверное и не играет большого значения - кэш вычитывается из DDRAM целыми линейками, а в нем уже все быстрее будет. Вдобавок в x86 система комманд такая, что от 1 до 9 что-ли байт инструкции, переменный размер, уже и код не выровненный by design.

А на ARM всяких не так давно перестали следить за выравниванием данных, раньше такое заканчивалось exception-ом аппаратным, который уж захочет или нет система исправлять, вопрос.

bugs-bunny
()

В спецификации языка Си возможность выравнивания типов на границу появилась начиная с C11.

Это неверно.

Ещё в K&R написано, что

6.8 Объединения

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

Конструкция для выравнивания структуры s по границе 4 байта выглядит так (я буду использовать C99 для наглядности):

union {
    struct {
        ...
    } s;
    uint32_t align;
};

Вы можете использовать любой тип для align (например массив фиксированной длины) чтобы задать любое выравнивание для s.

dsl
()
$ cat test.c
#include <stdalign.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

struct aligned {
	uint8_t a;
	uint8_t b;
	uint8_t c;
};

int
main(void)
{
	struct aligned *ptr;
	struct aligned alignas(128) stack;

	ptr = aligned_alloc(128, sizeof(*ptr));
	printf("ptr   -> address: %p size: %u \n", ptr, sizeof(*ptr));
	printf("stack -> address: %p size: %u \n", &stack, sizeof(stack));
	return 0;
}
$ ./test
ptr   -> address: 0x555851314080 size: 3
stack -> address: 0x7ffde8786380 size: 3
cumvillain
()
Последнее исправление: cumvillain (всего исправлений: 2)
Ответ на: комментарий от dsl

Вы можете использовать любой тип для align (например массив фиксированной длины) чтобы задать любое выравнивание для s.

Проблема в том, что это так же увеличивает размер.

cumvillain
()
Ответ на: комментарий от Shushundr

Не доказательство, может просто повезло.

Не доказательство чего? В стандарте описано, как использовать и alignas() и aligned_alloc().

cumvillain
()
Последнее исправление: cumvillain (всего исправлений: 1)
Ответ на: комментарий от Shushundr

Я ещё Rust хотел. Современный модный низкоуровневый язык, который используют даже в ядре.

А там ещё страдают:

#[repr(align(128))]
struct AlignedFoo(Foo);

#[repr(packed)]
struct Foo {
    a: u8,
    b: u8,
    c: u8,
}
cumvillain
()
Последнее исправление: cumvillain (всего исправлений: 1)