Похоже, что некоторые части выражения могут быть вычислены во время компиляции, в то время как другие части во время выполнения

#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: Если вы укажете свои собственные круглые скобки, то, конечно, они определяют подвыражение, которое по возможности будет свернуто с константой.