Минимально гарантированное постоянное складывание в C

Вопрос

Мне любопытно, есть ли какие-либо гарантии о постоянном сворачивании, выполняемом в C.

Где я посмотрел

Эта ссылка на сайте, репутация которого мне неизвестна, делает необоснованный комментарий:

Все компиляторы C могут складывать целочисленные константы, которые присутствуют после раскрытия макроса (требование ANSI C).

Но я не вижу ничего, например, в языке программирования C, второе издание (которое, я полагаю, было полностью обновлено, чтобы учесть все детали ANSI C). Но после проверки индекса на наличие ссылок на соответствующие слова я не нашел ничего, что сулило бы это. Страницы 38 и 209, в частности, подходят ближе, потому что они говорят, что любое выражение, которое может быть оценено во время компиляции, может использоваться там, где может использоваться константа (возможно, с некоторыми ограничениями, если мы достаточно педантичны), и там говорится, что такие выражения "может" оцениваться во время компиляции, а не "будет" / (некоторый синоним).

Я искал этот окончательный проект C89. Слова "сворачивание" и "сложенный" не дали результатов, а поиск "константное выражение" дал 63 совпадения, из которых я проверил около половины. Основная часть интереса, по-видимому, в основном совпадает с книгой (в ней используется слово "может" вместо "может", но в данном контексте это синонимы).

Оба из них, по-видимому, логически сильно намекают мне на то, что каждый компилятор ANSI C должен иметь базовые возможности свертывания констант, но в то же время, похоже, нет строгого запрета на компиляцию константных выражений в код, который вычисляет выражение во время выполнения (такая реализация по-прежнему получает выгоду от константных выражений, потому что компилятор может генерировать код, который вычисляет его один раз, а затем предполагает, что значение не изменится, и такая реализация может быть вызвана ограничениями в данной базовой архитектуре - например, в нескольких архитектурах RISC необходимо либо использовать две инструкции для инициализации определенных возможных значений, либо загрузить их из ячейки памяти).

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

мотивация

Я написал эти макросы для большей ясности семантики / намерений в некотором немного изменяющемся коде:

#define UCHAR_LOW_N_BITS_m(n) (unsigned char )(UCHAR_MAX >> (CHAR_BIT - (n)))
#define UCHAR_NTH_BIT_m(n) (unsigned char )(1 << (n))

..где n всегда целочисленный литерал. Я хочу успокоиться, услышать обнадеживающий голос, который говорит: "Все в порядке, каждый используемый компилятор C, который имеет удаленное значение, сворачивает эти константы для вас". (PS Я задал отдельный вопрос о том, UCHAR_NTH_BIT_m должен действовать так, как будто биты начинаются с 0-го или 1-го бита, надеюсь, в нужном месте.)

И да, нижний может быть превращен в отдельные макросы, например #define UCHAR_1ST_BIT_m (unsigned char )1 через #define UCHAR_3RD_BIT_m (unsigned char )4 или сколько бы мне ни понадобилось в коде - и хотя я не знаю, какой из них лучше, это, наверное, спорный вопрос, потому что, если я хочу быть хорошим программистом типа юрист-педантичник, я не могу точно избежать верхнего (нужно убедиться, что код работает правильно в тех реализациях DSP/ внедренного и древнего мэйнфрейма C).

2 ответа

Решение

В стандарте C термин время компиляции - это время трансляции, а события, которые происходят во время компиляции, могут быть описаны как во время трансляции. Вы можете искать стандарт для этих терминов.

Самое близкое в этом стандарте к вашей теме - определение константного выражения в разделе 6.6 (C11). Обзор:

Выражение константы может быть оценено во время перевода, а не во время выполнения, и, соответственно, может использоваться в любом месте, где может быть константа.

Обратите внимание, что "может быть" не означает "должен быть". Мы ожидаем, что компилятор хорошего качества будет оценивать константные выражения во время компиляции, хотя это не является абсолютным требованием.

Раздел 6.6 слишком большой, чтобы вставить сюда все, но, посоветовавшись со стандартом C (или с черновиком, таким как N1570), вы можете прочитать, какие выражения определены как константные выражения, и было бы разумно предположить, что ваш компилятор оценивает их в время компиляции.

Если n >= 0 && n < CHAR_BIT * sizeof(int) - 1, а также n является целочисленным константным выражением, то (unsigned char )(1 << (n)) является целочисленным константным выражением, потому что: его операнды являются целочисленными константами, он не использует один из операторов, которые запрещены в константных выражениях (например, ++), и приведение разрешено, поскольку оно преобразуется из целочисленного типа в другой целочисленный тип.

Если n находится вне этого диапазона, тогда выражение не является константным выражением и вызывает неопределенное поведение.

Ваше другое выражение похоже; это лед, пока CHAR_BIT - n попадает в тот же допустимый диапазон.

В целом, стандарт C не определяет, как что-то компилируется. Вы никогда не получите гарантию, что один способ реализовать что-то быстрее, чем другой. Нет никакой гарантии, что константное свертывание должно произойти, но компилятор должен иметь возможность оценивать константные выражения во время компиляции (для обработки #if директивы и деклараторы массива), очень вероятно, что компилятор делает постоянное сворачивание. В конце концов, нет смысла делать постоянное сворачивание, но не делать этого.

Другие вопросы по тегам