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

Вопрос

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

Где я смотрел

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

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

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

Я искал окончательный проект C89. Слова «folding» и «folded» не дали значимых результатов, а поиск «константное выражение» выдал 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 действовать так, как если бы or-one-indexed">биты начинаются с 0-го или 1-го бита, надеюсь, в правильном месте.)

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


person mtraceur    schedule 08.04.2016    source источник
comment
Это зависит от разработчика компилятора. Can по-американски означает «май» и означает, что вам разрешено, но вы не обязаны это делать. И да, любой хороший компилятор это сделает.   -  person Klas Lindbäck    schedule 08.04.2016
comment
Я не думаю, что использование can ограничивается американским английским языком.   -  person dreamlax    schedule 08.04.2016
comment
@KlasLindbäck: Я ценю разъяснение, но я знал: вот почему я сказал, что в основном это одно и то же, потому что я понял, что, хотя и была разница в формулировках, это означало, по сути, одно и то же. Я отредактирую его, чтобы уменьшить вероятность дальнейшего недопонимания.   -  person mtraceur    schedule 08.04.2016
comment
Итак, вы используете символ, набранный заглавными буквами (четкий знак того, что это макрос или символ препроцессора), с суффиксом _m в нижнем регистре, чтобы сказать, что символ является макросом? Хорошо.   -  person unwind    schedule 08.04.2016
comment
Язык программирования C, второе издание (которое, я полагаю, было тщательно обновлено с учетом всех деталей ANSI C). Это не так. 2-е обновление для совместимости с C90 было половинчатым. И, конечно же, стандарт C90 уже давно устарел.   -  person Lundin    schedule 08.04.2016
comment
@mtraceur Я хочу успокоиться, услышать обнадеживающий голос, говорящий, что все в порядке, каждый используемый компилятор C, который имеет какое-то отдаленное значение, свернет эти константы для вас Вы найдете утешение, если посмотрите на сгенерированную сборку.   -  person 2501    schedule 08.04.2016
comment
@ 2501: Сделано это, но хотя мои недавние экземпляры gcc производят обнадеживающую сборку: они, как правило, утешают компиляторы, выполняя множество оптимизаций, которые не выполнялись более старыми версиями компиляторов, которые некоторые бедолаги все еще могут использовать.   -  person mtraceur    schedule 08.04.2016
comment
@unwind: Итак, ты хочешь сделать ехидные замечания относительно стилистического выбора? Хорошо. Я надеюсь, что самодовольное удовлетворение, которое вы испытали, когда сделали этот комментарий, чтобы косвенно выразить свое осуждающее отношение, сделает мир лучше каким-то значимым, существенным образом. Мне нравится суффикс _m, особенно для функциональных макросов. Я меньше люблю символы, написанные заглавными буквами, но иногда использую их для констант, определенных препроцессором. Приведенные примеры являются историческим артефактом: они начинались как несколько простых макросов препроцессора (просто с заглавными буквами), затем я превратил их в макросы, подобные функциям, которые принимали аргумент n.   -  person mtraceur    schedule 08.04.2016
comment
@Lundin: Спасибо за разъяснение. Полезно знать, что это нерешительное обновление. Что касается устаревшего характера C89/C90, я бы очень хотел, чтобы это воплотилось в практическую реальность. Но совсем недавно, примерно в 2011 году или около того, gcc по умолчанию использовал C89 с расширениями GNU, Visual C Studio по-прежнему не поддерживает некоторые функции C99, о которых я слышал, и я знаю, что существует множество систем, застрявших на старых компиляторах. Поэтому я думаю, что стоит помнить о C89.   -  person mtraceur    schedule 08.04.2016
comment
@mtraceur Извините, если я прозвучал слишком ехидно. Я должен был выразить свое восхищение этим стилем менее агрессивно.   -  person unwind    schedule 08.04.2016
comment
@mtraceur GCC просто заставляет вас явно указывать ему, как себя вести, вот и все. Просто выполните -std=c11 -pedantic-errors, и он превратится в компилятор строгого стандарта C. А начиная с GCC 5.x.x, я думаю, по умолчанию используется gnu11, то есть расширения C11 +. Что касается Visual Studio, то это просто кусок дерьма.   -  person Lundin    schedule 08.04.2016
comment
@unwind: А, теперь я понимаю, что ты имеешь в виду. Спасибо. Честно говоря, даже с моей первоначальной интерпретацией ваш комментарий был мне полезен: он заставил меня потрудиться, чтобы вспомнить, почему я решил сделать это заглавными, _m исключенными, и это напомнило мне сделать еще один шаг в оценке недостатков и достоинств моих довольно идиосинкразических привычек именования.   -  person mtraceur    schedule 08.04.2016
comment
@Lundin: я думаю, что мы с вами одинаково относимся к преимуществам использования новых языковых стандартов. Я согласен с тем, что Visual C Studio следует справедливо осудить за то, что она не поддерживает полностью C11, не говоря уже о C99. Но в своих личных проектах я пишу код в надежде, что он будет полезен/полезен для других: поэтому я держу в уме людей, использующих некачественные компиляторы. Кроме того, задавая вопросы о деталях спецификации/стандарта, такие как этот вопрос, я думаю, что полезно знать, была ли данная формулировка добавлена ​​только в более новую версию спецификации, не так ли?   -  person mtraceur    schedule 08.04.2016


Ответы (2)


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

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

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

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

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

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

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

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

person M.M    schedule 08.04.2016

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

person fuz    schedule 08.04.2016
comment
Примерно так я и думал. Я поставил этому +1 и приму его через день или два, если не появится лучший ответ. - person mtraceur; 08.04.2016
comment
Содержимое директив #if оценивается препроцессором, а не компилятором, но константные выражения разрешены в объявлениях массивов, которые обрабатываются компилятором. - person supercat; 12.04.2016
comment
@supercat В стандарте не говорится, как реализован компилятор, он просто указывает серию фаз трансляции, первые несколько из которых обычно реализуются в препроцессоре. - person fuz; 12.04.2016