Почему это постоянное выражение не является постоянным - PullRequest
13 голосов
/ 02 июля 2019

У меня есть следующий список C:

static const int constant = (0 | ((((1 << 6) - 1) << ((((0 + 8) + 8) + 3) + 7)) & ((1) << ((((0 + 8) + 8) + 3) + 7))) | ((((1 << 7) - 1) << (((0 + 8) + 8) + 3)) & ((0) << (((0 + 8) + 8) + 3))) | ((((1 << 3) - 1) << ((0 + 8) + 8)) & ((0) << ((0 + 8) + 8))) | ((((1 << 8) - 1) << 0) & ((1) << 0)));

int main(int argc, char** argv)
{
    return constant;
}

Когда я пытаюсь скомпилировать это с GCC-9.1, используя эту командную строку:

gcc-9 -Werror -Wpedantic main.c

Я получаю эту ошибку:

main.c:1:29: error: initializer element is not a constant expression [-Werror=pedantic]

Почему это? Это ошибка компилятора? Ясно, что constant инициализируется константным выражением.

1 Ответ

14 голосов
/ 02 июля 2019

Я получаю эту ошибку:

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;
}

Обратите внимание, что тип результата побитового сдвига определяется только типом его левого операнда.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...