#c#
#c#
Вопрос:
Возможно, глупый вопрос, поскольку я, возможно, уже ответил на свой вопрос, но я просто хочу быть уверен, что я ничего не упускаю
Константные выражения вычисляются во время компиляции в пределах проверяемого контекста. Я думал, что следующее выражение не должно вычисляться во время компиляции, поскольку я предположил, что C # рассматривает конкретное выражение как постоянное выражение, только если все операнды в левой части являются константами:
int i= 100;
long u = (int.MaxValue 100 i); //error
Вместо этого, похоже, компилятор рассматривает любое подвыражение, где оба операнда являются константами, как постоянное выражение, даже если другие операнды в выражении не являются константами? Таким образом, компилятор может оценивать только часть выражения во время компиляции, в то время как оставшаяся часть выражения (которая содержит непостоянные значения) будет оцениваться во время выполнения -> Я предполагаю, что в следующем примере только (200 100)
оценивается во время компиляции
int i=100;
long l = int.MaxValue i ( 200 100 ); // works
Верны ли мои предположения?
спасибо
Комментарии:
1. 1: Совсем не похоже на глупый вопрос 🙂
Ответ №1:
классифицирует ли C # любое подвыражение, где оба операнда являются константами, как постоянное выражение, даже если другие операнды в выражении не являются константами?
Ответ на ваш вопрос «да», но важно четко понимать, что представляет собой «подвыражение».
Я предполагаю, что в «int.MaxValue i (200 100)» только (200 100) вычисляется во время компиляции
Правильно. Теперь, если бы вы сказали вместо этого «int.MaxValue i 200 100», то «200 100» вообще никогда бы не вычислялось, потому что это не подвыражение из-за ассоциативности.
Это немного сложно. Позвольте мне объяснить подробно.
Прежде всего, давайте проведем различие между константами времени компиляции де-юре и константами времени компиляции де-факто. Позвольте мне привести вам пример. Рассмотрим тело этого метода:
const int x = 123;
int y = x x;
if (y * 0 != 0) Console.WriteLine("WOO HOO");
M(ref y);
В C # 1 и 2, если вы скомпилировали это с включенной оптимизацией, это было бы скомпилировано так, как если бы вы написали:
int y = 246;
M(ref y);
Константа x обращается в нуль, y инициализируется выражением, свернутым с константой, и арифметический оптимизатор понимает, что любое локальное целое число, умноженное на ноль, никогда не бывает не равно нулю, поэтому он оптимизирует и это.
В C # 3 я случайно ввел ошибку в оптимизаторе, которая также не была исправлена в C # 4. В C # 3/4 мы бы сгенерировали это как
int y = 246;
bool b = false;
if (b) Console.WriteLine("WOO HOO");
M(ref y);
То есть арифметика оптимизирована, но мы не идем дальше и не оптимизируем «if (false)».
Разница в том, что свертка констант поведения на Х Х-это гарантированно произойдет во время компиляции, но постоянные складной поведения на частично-переменных выражения y*0-Нет.
Я сожалею об ошибке и приношу извинения. Однако причина, по которой я изменил это в C # 3 и случайно ввел ошибку в генератор кода, заключалась в том, чтобы исправить ошибку в семантическом анализаторе. В C # 2.0 это было законно:
int z;
int y = 246;
if (y * 0 == 0) z = 123;
Console.WriteLine(z);
Это не должно быть законным. Вы знаете, и я знаю, что y * 0 == 0 всегда будет иметь значение true, и поэтому z присваивается перед его чтением, но в спецификации сказано, что этот анализ должен выполняться, только если выражение в «if» является константой времени компиляции, а это не константа времени компиляции, потому что оно содержит переменную. Мы внесли решающее изменение для C # 3.0.
Итак, хорошо, давайте предположим, что вы понимаете разницу между константой де-юре, которая должна быть вычислена, потому что так сказано в спецификации, и константой де-факто, которая вычисляется, потому что оптимизатор умен. Ваш вопрос заключается в том, при каких обстоятельствах выражение может быть де-юре и де-факто частично «свернуто» во время компиляции? (Под «свернутым» я подразумеваю преобразование выражения, содержащего константы, в более простое выражение.)
Первое, что мы должны учитывать, это ассоциативность операторов. Только после того, как мы выполнили анализ ассоциативности и приоритета, мы знаем, что является подвыражением, а что нет. Рассмотрим
int y = 3;
int x = 2 - 1 y;
Сложение и вычитание являются левоассоциативными, как и большинство операторов в C #, так что это то же самое, что
int x = (2 - 1) y;
Теперь ясно, что (2 — 1) является де-юре постоянным выражением, поэтому оно сворачивается и становится 1.
Если, с другой стороны, вы сказали
int x = y 2 - 1;
Это
int x = (y 2) - 1;
и это не сворачивается, потому что это два непостоянных выражения.
Это может иметь реальный эффект в проверяемом контексте. Если y равно int.MaxValue — 1, то первая версия не будет переполняться, но вторая версия будет!
Теперь компилятору и оптимизатору разрешено сказать: «Ну, я случайно знаю, что это непроверенный контекст, и я случайно знаю, что могу безопасно преобразовать это в «y 1″, поэтому я это сделаю». Компилятор C # этого не делает, но дрожание может. В вашем конкретном примере,
long l = int.MaxValue i 200 100 ;
на самом деле компилятор C # генерирует код как
long l = ((int.MaxValue i) 200) 100 ;
И не
long l = (int.MaxValue i) 300;
Jitter может выбрать такую оптимизацию, если пожелает, и может доказать, что это безопасно.
Но
long l = int.MaxValue i (200 100);
конечно, будет сгенерировано как
long l = (int.MaxValue i) 300;
Тем не менее, мы выполняем требуемую оптимизацию строк в компиляторе C #! Если вы скажете
string y = whatever;
string x = y "A" "B" "C";
тогда вы можете подумать, ну, это левоассоциативное выражение, так что:
string x = ((y "A") "B") "C";
И, следовательно, не будет постоянного сворачивания. Однако на самом деле мы обнаруживаем эту ситуацию и во время компиляции выполняем сворачивание вправо, поэтому мы генерируем это так, как если бы вы написали
string x = y "ABC";
Тем самым экономя затраты на конкатенации во время выполнения. Оптимизатор объединения строк на самом деле достаточно сложен в распознавании различных шаблонов для склеивания строк во время компиляции.
Мы могли бы сделать то же самое для непроверенной арифметики. Мы просто не успели до этого додуматься.
Комментарии:
1. Я склоняюсь (и удаляю) перед большей мудростью. Спасибо за исключительный уровень детализации здесь.
2. Имеет смысл, что это
long l = int.MaxValue i 200 100 ;
генерируется компилятором какlong l = ((int.MaxValue i) 200) 100 ;
, но в моем примере я использовал круглые скобки вокруг200 300
. Таким образом,long l = int.MaxValue i (200 100);
также генерируется какlong l = ((int.MaxValue i) 200) 100 ;
или оно генерируется какlong l = ((int.MaxValue i) 300;
? Если последнее, то(200 300)
является де-юре постоянным выражением и, следовательно, должно оцениваться во время компиляции?!3. @EricLippert: «В C # 3 я случайно ввел ошибку в оптимизаторе, которая также не была исправлена в C # 4» Я уверен, что из-за этого на лице Билла Гейтса появилось несколько новых морщин: D. Придется сравнить его фотографии до C # 3 и после C # 3
4. @user702769: Если вы укажете свои собственные круглые скобки, то, конечно, они определяют подвыражение, которое по возможности будет свернуто с константой.