Я получаю эту ошибку:
main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]
Почему это? Это ошибка компилятора? Понятно, константа инициализирована
с постоянным выражением.
«Постоянное выражение» - это определенный термин в стандарте языка. Я подозреваю, что GCC использует его таким образом, поскольку стандарт требует, чтобы ваш инициализатор был постоянным выражением в этом смысле. Конечно, оценка вашего кода должна выполняться в этом свете.
Существует два языковых ограничения для константных выражений:
Выражения констант не должны содержать присваивания, приращения,
операторы декремента, вызова функции или запятой, кроме случаев, когда они
содержится в подвыражении, которое не оценивается.
и
Каждое константное выражение должно оцениваться как константа, которая находится в
диапазон представимых значений для его типа.
Первое не проблема для вас. Последний, однако, - это проблема в реализациях C, где тип int
имеет 31 или менее битов значения (включая GCC на большинстве платформ). Рассмотрим, в частности, это подвыражение:
(((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7))
... но для здравого смысла, давайте удалим некоторые ненужные скобки и упростим правую часть внешнего <<
, чтобы получить это, которое сохраняет соответствующие характеристики:
((1 << 6) - 1) << 26
Все отдельные числовые константы имеют тип int
, поэтому так же поступают все промежуточные результаты (где «26» в упрощенном варианте соответствует такому промежуточному результату в исходном выражении). Арифметически корректный результат для этого левого сдвига требует как минимум 32 значения, и, поскольку у вашего int
(вероятно) не так много, так как один знак зарезервирован для знака, поведение не определено.
Таким образом, здесь нет ошибки компилятора, хотя у вас могут быть основания для жалобы на качество реализации. Аналогично, ни один компилятор, который принимает код без предупреждения или ошибки, по этой причине не глючит. В другом смысле ваш код нарушает языковые ограничения, и в этом смысле компилятор обязан выдавать диагностическое сообщение, хотя выбранное им кажется неверным.
Более того, другие комментарии по этому вопросу, кажется, подтверждают, что переполнение связано с ошибкой, поскольку изменение вызываемого выражения с использования (1 << 6)
на (1 << 5)
или (1u << 6)
разрешает ошибку для других, кто мог бы воспроизвести это. Оба дают общее выражение без каких-либо подвыражений с неопределенным поведением.
Обратите внимание, что почти всегда лучше избегать целочисленных типов со знаком при выполнении побитовой манипуляции. Таким образом, пренебрегая каким-либо воздействием на более крупную программу, из которой это было получено, я был бы склонен переписать вашу примерную программу как
static const unsigned int constant = (0
| ((((1u << 6) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1u) << ((((0 + 8) + 8) + 3) + 7)))
| ((((1u << 7) - 1) << (((0 + 8) + 8) + 3)) & ((0u) << (((0 + 8) + 8) + 3)))
| ((((1u << 3) - 1) << ((0 + 8) + 8)) & ((0u) << ((0 + 8) + 8)))
| ((((1u << 8) - 1) << 0) & ((1u) << 0)));
int main(void) {
// There's a potential issue with the conversion of the return value, too, but it
// does not affect the particular expression at issue here.
return constant;
}
Обратите внимание, что тип результата побитового сдвига определяется только типом его левого операнда.