Основная проблема заключается в том, что компилятор C и компоновщик не согласовывают выравнивание структур.
Для содержимого раздела, скажем foo
, которое должно рассматриваться как один массивкомпилятор C, и компоновщик, и компилятор C, должны согласовать размер и выравнивание каждой структуры.Проблема заключается в том, что компоновщик обычно использует гораздо большее выравнивание, чем компилятор C, поэтому последовательные символы, размещенные в разделе, имеют более высокое выравнивание, чем ожидает компилятор C.
Решение состоит в том, чтобы обеспечитькомпилятор C и компоновщик согласовывают выравнивание символов, размещенных в разделе.
Например, если у вас есть, например,
static struct {
int i;
double d;
char c;
float f;
} foo[] __attribute__((__used__, __section__("foo"))) = {
{ 1, 1.0, '1', 1.0f },
{ 2, 2.0, '2', 2.0f }
};
, то символ, размещенный компоновщиком:foo
, и он будет интерпретироваться как определенный им компилятор Си.Однако, если у нас есть
static struct {
int i;
double d;
char c;
float f;
} foo1 __attribute__((__used__, __section__("foo"))) = {
1, 1.0, '1', 1.0f
};
static struct {
int i;
double d;
char c;
float f;
} foo2 __attribute__((__used__, __section__("foo"))) = {
2, 2.0, '2', 2.0f
};
, тогда компоновщик размещает foo1
и foo2
, используя любое выравнивание, которое он выберет;и чтобы рассматривать весь раздел foo
как массив, наше определение структур C должно иметь размер или выравнивание, совпадающее с выравниванием компоновщика.
Решение состоит не в том, чтобы упаковать структуры, нодополнить или выровнять их в соответствии с выравниванием, которое фактически использует линкерили указать компоновщику использовать такое же выравнивание для раздела foo, которое компилятор C использует для структур.
Существует множество способов достижения этого.Некоторые предлагают использовать скрипт компоновщика, но я не согласен: я предпочитаю выравнивать (используя __attribute__((__aligned__(size)))
) или дополнять (используя, например, конечный unsigned char padding[bytes];
) структуру, потому что это делает код более переносимым между архитектурами и компиляторами (и большинствоглавное, версии компилятора) по моему опыту.Другие, возможно, не согласны, но я могу только прокомментировать мой опыт и то, что я считаю наиболее подходящим.
Поскольку выравнивание компоновщика для раздела может измениться, мы, безусловно, хотим, чтобы его было легко определить при компиляции.время.Самый простой вариант - определить макрос, скажем, SECTION_ALIGNMENT
, который можно переопределить во время компиляции (например, с помощью опции -DSECTION_ALIGNMENT=32
gcc).В заголовочном файле, если он не определен, он должен по умолчанию использовать известные значения (8 для 32-битных арок, 16 для 64-битных арок в Linux, я считаю):
#ifndef SECTION_ALIGNMENT
#if defined(__LP64__)
#define SECTION_ALIGNMENT 16
#else
#define SECTION_ALIGNMENT 8
#endif
#endif
и CКомпилятору сообщают, что каждая такая структура имеет это выравнивание
struct foo {
/* ... Fields ... */
} __attribute__((__section__("foo"), __aligned__(SECTION_ALIGNMENT)));
, так что и компилятор C, и компоновщик согласовывают размер и выравнивание каждой такой структуры, размещенной в разделе foo
.
Обратите внимание, что мой связанный ответ имеет рабочий пример RPN калькулятора, использующего этот точный механизм для "регистрации" операторов, поддерживаемых калькулятором.Если есть какие-либо возражения против содержания этого ответа, я был бы очень признателен, если бы кто-нибудь сначала проверил этот реальный пример.