__attribute__ ((__aligned__)) не работает со статическими переменными

Это сводило меня с ума в течение нескольких дней. Я не могу заставить массив выровняться по 16, если объявлю его как static.

Любая помощь очень ценится.

Пересмотренный вариант:

#include <stdio.h>
#include <assert.h>

#define MAX_INPUTS 250

int main()
{
float input[MAX_INPUTS] __attribute__ ((__aligned__(16)));
printf("Address of input: %p\n", input);

printf("Assert1: %x\n", ( ((int) (input))      )        );
printf("Assert2: %x\n", ( ((int) (input)) % 16 )        );
printf("Assert3: %x\n", ( ((int) (input)) % 16 ) == 0   );

assert (     ( ((int) (input))      )        );  
assert (     ( ((int) (input)) % 16 )        );  /* Fails */
assert (     ( ((int) (input)) % 16 ) == 0   );  /* Passes */

return 0;
}

Результат:

Address of input: 0022FB70
Assert1: 22fb70
Assert2: 0
Assert3: 1
Assertion failed: ( ((int) (input)) % 16 ), file aligntest.c, line 16

Как и следовало ожидать, Assert 2 терпит неудачу, потому что адрес заканчивается на 0. Однако с:

static float input[MAX_INPUTS] __attribute__ ((__aligned__(16)));

вывод:

Address of input: 00404028
Assert1: 404028
Assert2: 8
Assert3: 1
Assertion failed: ( ((int) (input)) % 16 ), file aligntest.c, line 16

Утверждение 2 по-прежнему не выполняется, хотя результат не равен нулю. Когда Assert2 закомментирован, Assert3 проходит (с объявлением static или без него), и программа нормально завершается.

Я использую MinGw gcc 4.4.0 на Intel Core 2 Duo с XP Pro.


person Ian Shaw    schedule 23.09.2009    source источник
comment
@Ian, посмотрите дополнение к ответу @pmg - я думаю, вы найдете там достаточно хорошо описанную проблему (и решение, заключающееся в том, чтобы ввести volatile, чтобы остановить gcc в его безумном стремлении к оптимизации).   -  person paxdiablo    schedule 24.09.2009
comment
@Ian, по поводу обновления: assert2 всегда будет давать сбой по той же причине, по которой assert3 будет всегда проходить (независимо от того, действительно ли адрес выровнен компоновщиком или нет). GCC думает, что он будет выровнен и отбросит assert1/3 и, возможно, оптимизирует assert2 в assert(0).   -  person paxdiablo    schedule 24.09.2009
comment
Проверьте последнее обновление моего (2-го) ответа   -  person pmg    schedule 25.09.2009
comment
@pmg - я вижу, что вы делаете с принудительным выравниванием, и это работает, так что спасибо за это. Я должен обсудить с моим коллегой (у которого нет этой проблемы), является ли это бегуном для проекта. Возможно, это должно быть хотя бы временное исправление, пока мы не выясним, почему моя система дает сбой там, где работает его. Спасибо еще раз   -  person Ian Shaw    schedule 25.09.2009


Ответы (4)


На моей рабочей машине (Windows Vista, MinGW gcc 4.3.2) ваш код не создал ассемблера для утверждений ни на одном уровне оптимизации!

Чтобы сгенерировать утверждения, мне пришлось придумать переменную volatile int и скомпилировать с флагом -O0.

int main(void) {
  float input[MAX_INPUTS] __attribute__ ((__aligned__(16)));
  static float input_static[MAX_INPUTS] __attribute__ ((__aligned__(16)));
  volatile int addr_as_int;

  printf("Address of input: %p\n", &input);
  addr_as_int = (int)input;
  print_pointer(input);
  print_int(addr_as_int);
  printf("normal int: %08x; int%%16: %02x\n", addr_as_int, addr_as_int%16);
  printf("Assert: %d\n", (addr_as_int % 16) == 0);
  assert((addr_as_int % 16) == 0); /* Passes */

  printf("Address of input_static: %p\n", &input_static);
  addr_as_int = (int)input_static;
  print_pointer(input_static);
  print_int(addr_as_int);
  printf("static int: %08x; int%%16: %02x\n", addr_as_int, (addr_as_int)%16);
  printf("Assert: %d\n", (addr_as_int % 16) == 0);
  assert((addr_as_int % 16) == 0); /* Does not Pass */

  return 0;
}

Я понятия не имею, почему компилятор решил удалить утверждения из объектного файла. Быстрый поиск в гугле ничего интересного не выявил.

Обновление 1 (добавлено @Pax по предложению @Falaina — мы все предлагаем вам принять это, если оно окажется правдой):

На самом деле я думаю, что @Falaina прибила это в комментарии к ответу @Pax:

Просто предложения. Вы компилируете с оптимизацией? Возможно, компилятор пытается быть умным и говорит: «Эй, эта переменная выровнена по 16 байтам, очевидно, адрес % 16 равен 0» и заменяет все ваши проверки на 1. Просто мысль.

Вот объяснение. GCC выясняет из исходного кода, что ввод действительно (должен быть) выровнен по 16 байтам. Это достаточно умно, чтобы вообще отбросить assert и просто распечатать 1 вместо printf.

Однако на этапе компоновки компоновщик не может гарантировать выравнивание до 16 байтов, вместо этого выбирая 8, потому что (от @Pax):

Обратите внимание, что эффективность выровненных атрибутов может быть ограничена внутренними ограничениями вашего компоновщика. Во многих системах компоновщик может организовать выравнивание переменных только до определенного максимального выравнивания. (Для некоторых компоновщиков максимальное поддерживаемое выравнивание может быть очень-очень маленьким.) Если ваш компоновщик может выровнять переменные только до максимального 8-байтового выравнивания, то указаниеalign(16) в __attribute__ все равно предоставит вам только 8 байтов. байтовое выравнивание. См. документацию вашего компоновщика для получения дополнительной информации.

К тому времени уже слишком поздно возвращать в код assert и неоптимизированные printf. Таким образом, фактический исполняемый файл не будет утверждать (поскольку они были удалены) и будет печатать оптимизированную 1, а не вычислять ее во время выполнения.

Причина, по которой volatile исправляет это в моем ответе, заключается в том, что GCC не будет оптимизировать выражения, содержащие изменчивые компоненты. Он оставляет asserts и правильно вычисляет аргументы printf во время выполнения.


Вы можете вручную выровнять свой массив, если не возражаете объявить его немного больше, чем это необходимо:

#include <assert.h>
#include <stdio.h>

#define MAX_INPUTS 250

void *force_align(void *base, size_t s, int align) {
  size_t x;
  int k = 0;
  x = (size_t)base;
  while ((k < align / (int)s) && (x % align)) {
    k++;
    x += s;
  }
  if (k == align) return NULL;
#if 0
  printf("%d elements 'discarded'\n", k);
#endif
  return (void*)((size_t)base + k*s);
}

int main(void) {
  #define ALIGNMENT_REQ 16
  #define EXTRA_ALIGN_REQ (ALIGNMENT_REQ / sizeof (float))
  static float misaligned_input[MAX_INPUTS + EXTRA_ALIGN_REQ]
        __attribute__ ((__aligned__(ALIGNMENT_REQ)));
  float *input;

  /* manual alignment, check for NULL */
  assert( (input = force_align(misaligned_input, sizeof *input, ALIGNMENT_REQ)) );

  printf("Address of misaligned input: %p\n", misaligned_input);
  printf("Address of input: %p\n", input);
  printf("Assert1: %x\n", ( ((int) (input))                 )      );
  printf("Assert2: %x\n", ( ((int) (input)) % ALIGNMENT_REQ )      );
  printf("Assert3: %x\n", ( ((int) (input)) % ALIGNMENT_REQ ) == 0 );
  assert ( ( ((int) (input))                 )      );
#if 0
  assert ( ( ((int) (input)) % ALIGNMENT_REQ )      );  /* Fails */
#endif
  assert ( ( ((int) (input)) % ALIGNMENT_REQ ) == 0 );  /* Passes */

  return 0;
}
person pmg    schedule 24.09.2009
comment
@pmg, между этим ответом и комментарием @Falaina к моему ответу вы, ребята, прибили его. Причина, по которой вам нужна volatile - без нее gcc предполагает, что она выровнена (16) и оптимизирует утверждения (и просто печатает 1). Но компоновщик этого не учитывает, хотя к тому времени уже слишком поздно менять объектный файл. Смотрите мое обновление к вашему ответу, чтобы узнать о кровавых подробностях. - person paxdiablo; 24.09.2009
comment
Что ж, я не компилировал с какой-либо оптимизацией свою тестовую программу, но мне, безусловно, захочется, так что это имеет смысл. Мне трудно поверить, что компоновщик выровняет автоматические переменные, но не статические. По крайней мере, не намеренно. Я попытался найти, какое выравнивание он поддерживает, но вычеркнул. Это заставляет меня задаться вопросом, есть ли какие-то специфичные для системы настройки компиляции, которые мне не хватает. Попробую посмотреть, что получится при компиляции на другом ПК. - person Ian Shaw; 25.09.2009
comment
@Ian Shaw - компоновщик не отвечает за выравнивание автоматических переменных. Компилятор может генерировать сборку, которая гарантирует выравнивание для переменной стека (например, если я выполняю побитовое И для указателя стека с -16 перед выделением стека, я гарантирую выравнивание), компилятор не гарантирует то же самое для глобальных переменных (а статика реализуется как глобальная переменная). в конце концов), лучшее, что он может сделать, это сообщить компоновщику, что ему нужно определенное выравнивание для переменной. - person Falaina; 25.09.2009
comment
Кроме того, я не знаю, пробовали ли вы уже, Ян, но вам определенно следует попытаться перезапустить свою программу с volatile и сообщить о результатах. Если это не удается правильно при утверждении, проблема решена (хотя вам может не повезти с выравниванием ваших переменных :( - person Falaina; 25.09.2009
comment
В качестве альтернативы вы можете (1) иметь массив структур: typedef struct {float input;float мусор} inp_str; inp_str ввод[250]; и используйте input[].input чтобы убедиться, что ввод выровнен; или (2) сделайте ввод массива [MAX_INPUTS*2] и используйте только четные индексы. Но теперь мы входим в серьезную территорию кладжа :-) - person paxdiablo; 25.09.2009
comment
@Falaina - вывод этой версии работает так, как ожидалось - утверждение завершается ошибкой, когда входной адрес % 16 отличен от нуля: это работает для любого уровня оптимизации, единственная разница заключается в том, что адрес сообщается для ввода. Такое использование volatile решает проблему утверждения, но, к сожалению, не является основной проблемой получения выровненного массива, как вы заметили. - person Ian Shaw; 25.09.2009
comment
@Pax - я не уверен, что ваши предложения подойдут для моего приложения. Я захочу загрузить непрерывные блоки из четырех чисел с плавающей запятой в 128-битный регистр SSE, и я думаю, что это будет невозможно, если я заполню структуру неиспользуемыми элементами. - person Ian Shaw; 25.09.2009
comment
Мой коллега скомпилировал тот же файл на своем ПК и получил выровненные входные данные. Мы оба используем один и тот же компоновщик: GNU ld (GNU Binutils) 2.19.1 Я начинаю думать, что это должно быть что-то в моей системе. - person Ian Shaw; 25.09.2009

Из здесь:

Обратите внимание, что эффективность выровненных атрибутов может быть ограничена внутренними ограничениями вашего компоновщика. Во многих системах компоновщик может организовать выравнивание переменных только до определенного максимального выравнивания. (Для некоторых компоновщиков максимальное поддерживаемое выравнивание может быть очень-очень маленьким.) Если ваш компоновщик может выровнять переменные только до максимального 8-байтового выравнивания, то указаниеalign(16) в __attribute__ все равно предоставит вам только 8 байтов. байтовое выравнивание. См. документацию вашего компоновщика для получения дополнительной информации.

Я знаю, почему утверждение не выполняется, это потому, что выражение истинно. Не знаю точно, почему выражение истинно, но в подобных случаях вам следует разобрать его. Добавьте их в свои операторы отладки:

printf("Assert1: %x\n", ( ((int) (input))));
printf("Assert2: %x\n", ( ((int) (input)) % 16 ));
printf("Assert3: %x\n", ( ((int) (input)) % 16 ) == 0);

и покажите нам результаты.

Также проверьте, какую версию gcc вы используете — 4.3.1 и более ранние версии имеют проблема с выравниванием. Похоже, что в Cygwin есть пакеты gcc3 и gcc4, если вы используете именно их. Если нет, еще проверьте версию.

Обновление 1: На самом деле я думаю, что @Falaina уловила это в комментарии ниже. Вот правдоподобное объяснение.

GCC выясняет из исходного кода, что ввод действительно (должен быть) выровнен по 16 байтам. Это достаточно умно, чтобы вообще отказаться от утверждений и просто распечатать 1 для printf.

Однако на этапе компоновки компоновщик (не такой способный, как GCC) не может гарантировать выравнивание до 16 байтов, вместо этого выбирая 8 (см. мою цитату выше). К тому времени уже слишком поздно возвращать в код утверждения и неоптимизированные printfs. Таким образом, фактический исполняемый файл не будет утверждать (поскольку они были удалены) и будет печатать оптимизированную 1, а не вычислять ее во время выполнения.

Причина, по которой volatile исправляет это в ответе @pmg, заключается в том, что GCC не оптимизирует выражения, содержащие volatile-компоненты. Он оставляет утверждения и правильно вычисляет аргументы печати во время выполнения.

Если это окажется так, то это, без сомнения, одна из самых коварных проблем, которые я видел. Я не решаюсь назвать это ошибкой, так как и gcc, и ld действуют так, как рекламируется - это комбинация факторов, которые портят ситуацию.

person Community    schedule 23.09.2009
comment
Assert1: 404028 Assert2: 8 Я использую MinGW и только что обновил gcc (GCC) 4.3.0 20080305 (альфа-тестирование) mingw-20080502 до gcc (GCC) 4.4.0, но безрезультатно. - person Ian Shaw; 24.09.2009
comment
Вы все еще получаете 8 для assert2 и 1 для исходного утверждения? Пожалуйста, подтвердите, потому что это совершенно неправильно. Пожалуйста, добавьте утверждение 3 и перепроверьте. - person paxdiablo; 24.09.2009
comment
Ошибка Assert2, несмотря на то, что printf сообщает 8. ‹pre› Адрес ввода: 00404028 Assert1: 404028 Assert2: 8 Assert3: 1 Ошибка утверждения: ( ((int) (input)) % 16 ), файл aligntest.c, строка 25 ‹/pre› Assert3 проходит (когда я закомментирую Assert2) - person Ian Shaw; 24.09.2009
comment
Просто предложения. Вы компилируете с оптимизацией? Возможно, компилятор пытается быть умным и говорит: «Эй, эта переменная выровнена по 16 байтам, очевидно, что адрес % 16 равен 0 и заменяет все ваши проверки на 1. Просто мысль. - person Falaina; 24.09.2009
comment
У тебя получилось, я полагаю, @Falaina. Я был бы рад, если бы вы скопировали мое обновление 1 в свой собственный ответ и приняли его, поскольку именно ваше понимание заставило его щелкнуть. @pmg тоже заслуживает нескольких голосов, так как этот ответ дал еще одну важную часть головоломки. - person paxdiablo; 24.09.2009
comment
Ах, это не обязательно, плюс вы предоставили очень полезный разбор. Если это действительно то, что происходит, я думаю, я бы посчитал это ошибкой. В то время как GCC (ну, это разработчики), безусловно, может привести аргумент, который я просил о выравнивании по 16 байтам, поэтому я могу принять его для остальной части компиляции, но это, безусловно, менее консервативно, чем я хотел бы, чтобы мой компилятор был, особенно в отношении что-то вроде утверждений. - person Falaina; 24.09.2009
comment
Вы также можете взять представителя. Несмотря на то, что я отредактировал свой ответ, он все равно ушел из вики сообщества, я уже достиг своего дневного предела в 200, и мне больше не нужен представитель - я на самом деле беру творческий отпуск, когда я набрал 50 000, но мы посмотрим сколько протянет :-) - person paxdiablo; 24.09.2009
comment
Ха-ха, я собираюсь уйти, так что я пропущу, но я бы посоветовал @pmg отредактировать вашу разбивку в своем ответе, так как он проделал немало работы, чтобы выяснить, что сработало, а что нет. - person Falaina; 24.09.2009

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

  • Уберите & из выражения адреса в первом printf(). Хотя (&array) и (array) — это одно и то же выражение с одним и тем же значением в C, кажется совершенно неправильным писать их в обоих вариантах в пределах нескольких строк друг от друга. Когда-нибудь просто забота о мелочах в программе спасет вас от серьезной ошибки, или кто-то изменит тип операнда на что-то, где дополнительный & делает двойной косвенный указатель. В любом случае, стиль имеет значение. Я не могу это доказать, но я уверен, что это так.

  • Делайте вещи действительно простыми. Сосредоточьтесь только на случае отказа с классом хранения static, который проходит невозможное утверждение. Создайте новый каталог, в котором абсолютно ничего нет, скопируйте в него только программу на случай сбоя, а затем, в зависимости от CLI, попробуйте запустить ее с помощью ./whatever или .\whatever. Убедитесь, что ничего не запускается, пока вы не скомпилируете его. Убедитесь, что вы управляете тем, чем вы себя считаете.

  • Сообщите нам, какую именно среду gnu вы используете в XP, их несколько.

person DigitalRoss    schedule 23.09.2009

Преобразование из pointer to array of float в int не обязательно имеет смысл. Если pointer to array of floats больше, чем int, вы теряете некоторую информацию, которая может быть "младшими битами" указателя.

Попробуй это:

#include <stdio.h>
#include <string.h>

void print_pointer(void *ptr) {
  unsigned char data[sizeof (void*)];
  size_t k;

  memmove(data, &ptr, sizeof (void*));
  printf("ptr: ");
  for (k=0; k<sizeof (void*); k++) {
    printf(" %02x", data[k]);
  }
  puts("");
}

Сделайте то же самое для int

void print_int(int value) { /* ... */ }

и сравните свои выводы.

person pmg    schedule 23.09.2009
comment
Я получаю одинаковый результат для обеих функций. номер: 28 40 40 00 - person Ian Shaw; 24.09.2009