Как правильно разрешить увеличение выравнивания указателя с помощью clang?

Рассмотрим следующие структуры:

typedef struct {
  uint32_t foo;
  uint32_t bar;
} first_struct_t;

typedef struct {
  first_struct_t f;
  uint8_t *p;
  uint8_t buf[];
} second_struct_t;

Однако позже в моем коде происходит следующее назначение:

typedef struct {
  first_struct_t *f;
  // ...
} some_struct;

int function_before_foo(some_struct *p) {
  p->f = (second_struct_t *) malloc(sizeof(second_struct_t));
  // ...
}

int function_foo(some_struct *p) {
  second_struct_t *s = (second_struct_t *) p->f;
  // ...
}

И он генерирует следующую ошибку из-за -Wcast-align:

'second_struct_t *' increases required alignment from 4 to 8 [-Werror,-Wcast-align]
second_struct_t *s = (second_struct_t *) p->f;

Решение состоит в том, чтобы привести его к void *, но это, похоже, только маскирует проблему. Какое самое чистое решение для этого?

EDIT: Это происходит только с clang, а не с GCC, независимо от того, что оба компилятора имеют флаг -Wcast-align.


person MarkP    schedule 19.10.2015    source источник
comment
some_struct *p и struct_second_t *s не взаимозаменяемы (указатель на структуру всегда указывает на ее первый элемент, а это не так). Вы имеете в виду first_struct_t *s = (first_struct_t *) p->f;?   -  person David Ranieri    schedule 19.10.2015
comment
Разве p->f не first_struct_t? Не first_struct_t *?   -  person Andrew Henle    schedule 19.10.2015
comment
Вы правы. Я исправил неверную формулировку.   -  person MarkP    schedule 19.10.2015
comment
stackoverflow.com/questions/10951039/ Попробуйте принудительно first_struct_t a Выравнивание по 8 байт.   -  person user3528438    schedule 19.10.2015


Ответы (1)


Я собираюсь предположить, что когда у вас есть struct_second_t, вы на самом деле имели в виду second_struct_t, иначе ваш код не скомпилируется.

Проблема в том, что f в some_struct является указателем на first_struct_t, а не на second_struct_t.

Я должен добавить, что приведение в struct_second_t *s = (struct_second_t *) p->f; скрывает предупреждающее сообщение. В общем, если вам нужно привести один указатель к другому, вы чаще всего будете вызывать неопределенное поведение. Это не всегда верно, но является довольно хорошим ориентиром.

В ответ на комментарий.

Сначала кажется, что вы не получите это предупреждение с gcc для x86 (32 и 64 бит), поскольку нет требований к выравниванию для регистров общего назначения, хотя выравнивание может повысить производительность (см. это SO post для получения дополнительной информации). Что касается того, почему clang выдает это предупреждение, возможно, из-за производительности или у них нет исключения, как у gcc для x86.

Во-вторых, то, что вы пытаетесь выполнить, похоже на то, что макрос container_of делает в ядре Linux. container_of обычно определяется как:

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

При поиске единственное, что я нашел, что решает вашу проблему, это эта фиксация, чтобы изменили свою версию container_of, включив приведение к void*.

По сути, я считаю, что проблема в том, что вы знаете, что p->f на самом деле указывает на second_struct_t, которого нет у вашего компилятора, и поэтому выдает предупреждение. Так что вы можете либо не делать этого, либо использовать void*.

Дополнительный:

Кажется, что Mozilla решает проблему, также приводя к void*:

Наконец, я бы рекомендовал взглянуть на container_of и использовать его вместо того, чтобы полагаться на тот факт, что первый элемент структуры является другой структурой. Таким образом, ваш код будет более устойчивым, если кто-то еще изменит порядок элементов структуры, он все равно будет работать. Вам нужно будет добавить приведение void* к container_of, чтобы избежать предупреждения. Обратите внимание, что в архитектурах с проблемами выравнивания у вас не должно быть проблем во время выполнения, если вы всегда правильно меняете тип между дочерним и родительским.

person missimer    schedule 19.10.2015
comment
Я использую переменную ->f для хранения указателя на экземпляр second_struct_t. По сути, это дешевый способ реализации полиморфизма. Вы можете использовать ->f для доступа к first_struct_t, и если вы знаете об этом, вы можете получить доступ к second_struct_t. В этом случае я знаю о second_struct_t и готов использовать его таким образом. - person MarkP; 19.10.2015