«ошибка: элемент инициализатора не является константой», когда используется битное смещение, но не сложение или вычитание, в C - PullRequest
0 голосов
/ 09 июня 2018

Моя встроенная система имеет две области памяти.Я создал макросы для переключения между этими регионами.Я хотел бы иметь возможность выполнять эти макросы во время компиляции, но я получаю error: initializer element is not constant для определенных операций, а не для других.Я свел пример к этому.Это глобальные переменные:

__attribute__((section(".vmem_constant.data"))) unsigned int buf0[1024];
unsigned int buf_word_ptr = ((unsigned int)buf0)>>2; // doesn't work
unsigned int buf_word_ptr2 = ((unsigned int)buf0)/4; // doesn't work
unsigned int buf_word_ptr3 = ((((unsigned int)x)-0x40000)>>2); // original problem doesn't work
unsigned int works_1 = ((unsigned int)buf0) + 2; // works
unsigned int works_2 = buf0 + 16; // works

Кажется, что я не могу сделать деление или сдвиг, однако сложение или вычитание это нормально.

Я изначально столкнулся с этим, когда пыталсявычтите фиксированное смещение, а затем разделите на 4. Может быть, есть более простой способ сделать это?Я использую (GCC) 7.2.0

Ответы [ 2 ]

0 голосов
/ 10 июня 2018

Что касается чистого языка Си, вы не должны использовать зависящие от адреса значения в выражениях арифметических констант.Тот факт, что вы можете использовать (unsigned int) buf0 в инициализаторе для целого числа, является специфичным для компилятора расширением.

В этом расширении ограничения, наложенные на адрес констант, обычно по-прежнему применяются.Эти ограничения коренятся в возможностях реальных загрузчиков .В общем случае конкретный адрес вашего buf0 на самом деле не является константой времени компиляции.Его фактическое значение будет известно только во время загрузки.Загрузчик должен будет выполнить последние обновления ваших «константных выражений», которые зависят от этого адреса.Арифметические возможности загрузчика весьма ограничены.Погрузчики знают, как складывать и вычитать, но это все.По этой причине вам разрешено использовать сложение и вычитание в адресных константных выражениях (а также другие операторы, которые в конечном итоге сводятся к сложению или вычитанию адреса), но не более того.Загрузчики не могут выполнять смены.

Более того, тот факт, что ваш компилятор даже принимает (unsigned int) buf0 в некоторых из этих инициализаторов, является чистым совпадением.Видимо, на вашей платформе размер указателя такой же, как размер unsigned int.Если бы это было не так, преобразование из указателя в unsigned int должно было бы усечь или расширить значение.Загрузчики тоже не могут этого сделать, что означает, что если бы не это совпадение, все ваши объявления не скомпилировались.Вот почему, когда вы хотите преобразовать указатели в целые числа, лучше использовать uintptr_t вместо unsigned int.

0 голосов
/ 09 июня 2018

Строго говоря, все вышеперечисленные формы инициализации запрещены в стандарте C.

Объекты со статическим хранилищем допускается инициализировать с помощью следующих видов выражений (более подробно описанных здесь (со стандартными ссылками):

  1. выражение арифметической константы ;
  2. NULL константа указателя;
  3. выражение константы адреса ;
  4. выражение константы адреса некоторого полного типа объекта, плюс или минус целочисленное константное выражение.

В частности, арифметическое константное выражение может состоять из арифметических операторов, оператора sizeof и литеральных операндоварифметических типов.buf0 является переменной, а не литералом, и поэтому ни одно из выражений в примере не квалифицируется как выражение арифметической константы.Типы выражений 2, 3 и 4 также не применимы, поэтому компилятор может отклонить все формы инициализации, использующие buf0.

Это имеет смысл, поскольку адрес buf0 разрешается только по ссылке.время, а не во время компиляции, поэтому его нельзя использовать для составления постоянных выражений времени компиляции.

Однако gcc (и другие компиляторы C, включая clang и icc) разрешат две последние формы, когда целевые объектыширина адреса и адрес назначения int width одинаковы, что является расширением.Например, на x86-64 мы получили бы:

uint64_t fails = ((uint32_t)buf0) + 2; // fails
uint64_t works_1 = ((uint64_t)buf0) + 2; // works
uint64_t works_2 = (uint64_t)buf0 + 16ul; // works

И, если мы проверим сгенерированную сборку ( godbolt ), мы увидим расширенный код gcc для works_1 и works_2:

works_1:
  .quad buf0+2
works_2:
  .quad buf0+16

Ассемблер GNU допускает простую арифметику в вычислениях статических адресов с использованием + и -, но не позволяет использовать более сложные выражения.Теоретически, если ассемблер (и компоновщик) допускают более продвинутую адресную арифметику, такую ​​как сдвиг, компилятор C также может допускать это расширение (но не строго соответствует).

...