Блог пользователя iakolzin

Автор iakolzin, 13 лет назад, По-русски

Всем доброго времени суток. Сегодня я хочу рассказать вам одну очень поучительную и, увы, немного печальную историю. Я думаю, что всем, кто пишет на C++, будет полезно прочитать.

Итак, всего-то неделю назад мы вернулись с Чемпионата Урала. Как вы могли прочитать в письмах и обзорах, на чемпионате было два тура: игровой и основной. И если с основным все понятно, то с игровым все вышло весьма презабавно. Началась эта история с того, что, когда на подведении итогов тура показали результаты швейцарки, то мы не увидели себя в первых ~30 командах, которые поместились на основной монитор. Хотя наш бот был не слишком умный, но все же трудно было предположить, что все написали лучше. Мысль о том, что он где-то упал, отпала, т.к. и локально, и на сервере компилятор был одинаковый, а, значит, никаких "трудностей перевода". Ну да всякое бывает, на следующий день был основной тур, и мы забили.

Пару дней назад появились результаты. Увидев нашего бота на предпоследнем месте, удивлению нашему не было предела, т.к., если верить монитору, то он проиграл даже SampleBot'у, которого раздали всем командам. Это было просто невероятно, мы все-таки догадались против него поиграть. Опять же, падение почти исключалось, т.к. у кого-то он смог выиграть, т.е. хотя бы ход сделать успевал. Естественно мы скачали исходник, скомпилили, запустили и... Он выигрывал! Невероятно, но это был "тот самый" наш, самый что ни на есть разумный бот! Выиграв у команды N из топ-5(!!!), я решил наконец-то посмотреть, что же не так. К счастью, доступны были и логи проведенных на сервере матчей. Что же я увидел? Вместо нашей программы кто-то тупо складывал все шарики в левый столбец и через 16 ходов проигрывал. Естественно, мы подумали, что это какая-то нелепая ошибка и сразу написали организаторам.

Как вы думаете, мог ли ответ удивить нас еще сильнее, чем все произошедшее? Да, ответ буквально поверг в шок. Оказывается, тестирование на сервере проходило не совсем как локально, а именно, флаг /GL, который в студии в релизе включен по умолчанию, был отключен на сервере, и, утверждалось, что именно это было причиной столь странного поведения. Действительно, отключив этот флаг у себя, я получил то же, что и в том самом печальном логе. Кстати, в дебаге этот флаг тоже отключен (и включить его нельзя), однако она работала нормально. День прошел в поиске ошибки, стоит ли говорить, сколько вариантов ошибок я пытался найти?

Оказалось все довольно прозаично: наша программа делала перебор на два хода, соответственно, делала ход один раз, изменяя при этом игровое поле, потом ходила еще раз. Вот строчка: int score = FirstMove(tmp) + SecondMove(tmp); tmp - игровое поле, в функции передавалось по ссылке. Кажется, что все логично, но не тут-то было! Опытным путем было установлено, что в релизе в зависимости от этого пресловутого ключика (/GL) зависит порядок вызова функций. Т.е. у нас всегда функции вызывались в правильном (для людей, пишущих слева направо) порядке, а на сервере - в обратном. Разумеется, как только я разнес эту конструкцию на две операции, все стало работать. Вот такая вот подстава.

На самом деле, это не совсем непредсказуемая подстава. Ведь мы знали, что, например, в случае Func(a(), b()); сначала вызовется именно b(), а только потом a(). То есть такой код уже потенциально опасен. Засада в том, что программа работает правильно именно в случае по умолчанию, а падает в не совсем обычной ситуации. Тем не менее, это послужит хорошим уроком. Может быть, получить такую проблему при разработке большого проекта - зло еще большее, чем потеря приза.

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

Кроме того, выскажу свое пожелание организаторам игровых туров. Понятно, что проводить полное тестирование при каждой сдаче невозможно, да и не нужно. Но вполне можно было протестировать решение участников хотя бы на Sample-Bot'е. Знания вердикта, времени, памяти, количества ходов и рандсида было бы с высокой вероятностью достаточно для понимания того, работает ли программа на сервере так же, как и локально. Понятно, что такой случай, как у нас, было сложно предположить, но это пожелание на будущее.

  • Проголосовать: нравится
  • +66
  • Проголосовать: не нравится

13 лет назад, # |
  Проголосовать: нравится -8 Проголосовать: не нравится
Т.е. у вас на компьютере и на сервере был по разному настроен компилятор? Нет, разумеется, организаторы ничего и никому не должны, а стандарт языка ничего не утверждает и вы должны были это знать. Но все же, по-моему, одинаково настроенные среды на сервере и рабочих компах добавляет очков уважения к турниру.
  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Я не сказал сказал, что они что-то должны. Разве вам кажется глупостью проверять решение на одном боте? Уверен, что многие иного мнения, минимальная проверка работоспособности программы в реальных условиях точно не мешает. Кроме того, в Новосибирске так почему-то делают.

    Как правило, проблемы со стандартом проявляются при использовании разных компиляторов на сервере и на рабочей машине. Этот пост о том, что это не всегда так. Уверен, для кого-то он точно окажется полезен. 

    • 13 лет назад, # ^ |
        Проголосовать: нравится +9 Проголосовать: не нравится
      Я просто подумал, что это бы помогло улучшить олимпиаду. Организаторы не обязаны кормить участников обедами и приносить перекус в середине контеста. Но если они это делают, олимпиаду становится гораздо приятнее писать. Здесь то же самое.
13 лет назад, # |
  Проголосовать: нравится -15 Проголосовать: не нравится
C хабром не перепутал, %username%?
13 лет назад, # |
  Проголосовать: нравится 0 Проголосовать: не нравится
Длинный рассказ о том, что автор не знал языка, на котором писал олимпиаду. Что поучительного или печального ? 
  • 13 лет назад, # ^ |
      Проголосовать: нравится +12 Проголосовать: не нравится
    Знаете, в C++ и оптимизаторах достаточно много различных вещей, которые, вроде бы, должны выполняться именно так, а не иначе. Стандарты и компиляторы думаю иначе.
    Тут и возникают непонятнки.

    Неочевидно же с ходу, зачем компилятору может потребоваться менять порядок вызова функций в строке?
    • 13 лет назад, # ^ |
        Проголосовать: нравится -16 Проголосовать: не нравится
      Ну оптимизация тема сложная - для новичка достаточно знать что может понадобиться, об этом в любом нормальном учебнике пишут.
      • 13 лет назад, # ^ |
          Проголосовать: нравится +11 Проголосовать: не нравится
        Я имел ввиду оптимизатор, который -O2, -O?.
      • 13 лет назад, # ^ |
          Проголосовать: нравится +5 Проголосовать: не нравится
        Как отличить нормальные учебники от ненормальных? Да и даже хороший учебник может со временем устареть. Поэтому здесь я за экспериментальный метод.
        • 13 лет назад, # ^ |
            Проголосовать: нравится +5 Проголосовать: не нравится
          А я за следование стандарту. И у Страуструпа все, что надо для следования стандарту отлично расписано. Я уже давно привык не закладываться на порядок не логических операций
          • 13 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            Я помню, что MSVS 6.0 противоречила стандарту (правда, уже не помню в чём, но на контестах я на это нарывался, факт). Если современные версии (2005 и 2008) ему не противоречат, то это, конечно, хорошо.
            • 13 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится
              Я только замечал что VS позволяет некоторые вещи, которые стандартом не определены. Ну и да, export никто не умеет, но он на олимпиадах бессмысленен
              А, еще при #include<cstdio> нельзя using namespace std; , да
              • 13 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                Чегоооо? Да я всё время так пишу! Или ты это про старую "Студию"? Но даже там что-то такого убойного косяка не припомню...
                Но вообще да, всё плохо вроде было с STL и с 64-битными числами.
                • 13 лет назад, # ^ |
                    Проголосовать: нравится 0 Проголосовать: не нравится
                  Про старую
                  Там чтобы заюзать std надо было какой-нибудь из плюсовых хедеров заинклудить
            • 13 лет назад, # ^ |
                Проголосовать: нравится 0 Проголосовать: не нравится

              > Если современные версии (2005 и 2008)

               

              ...

              • 13 лет назад, # ^ |
                  Проголосовать: нравится 0 Проголосовать: не нравится
                Современные - те, которые на тестирующих серверах. А эта ваша 2010 - сейчас ещё пока сверхсовременная :)
        • 13 лет назад, # ^ |
            Проголосовать: нравится -6 Проголосовать: не нравится
          Экспериментальный метод изучения С++ вместо страуструпа ? Комментарии излишни
          • 13 лет назад, # ^ |
              Проголосовать: нравится 0 Проголосовать: не нравится
            Ну всё-таки не вместо, а в дополнение :)
            Идея в том, что ни одной книге нельзя безоговорочно верить. Стоит помнить, что повсюду вражеские шпионы!
            • 13 лет назад, # ^ |
                Проголосовать: нравится +13 Проголосовать: не нравится
              Хотелось бы услышать, почему не верить учебнику в котором большими буквами кричат, что порядок вычисления аргументов функции не определён ? 
              • 13 лет назад, # ^ |
                  Проголосовать: нравится +2 Проголосовать: не нравится
                Речь не об этом, а об учебниках, в которых пишут, как конкретно производится оптимизация.
                • 13 лет назад, # ^ |
                    Проголосовать: нравится +11 Проголосовать: не нравится
                  Речь как раз то об этом, хотя вы могли и потерять нить рассуждений 
                  • 13 лет назад, # ^ |
                      Проголосовать: нравится -8 Проголосовать: не нравится
                    Хм, возможно... Но если так, то Вы тоже отчасти в этом виноваты - Ваш коммент допускает двоякое понимание.
        • 13 лет назад, # ^ |
            Проголосовать: нравится +14 Проголосовать: не нравится
          Анекдот вспомнил в тему. Придётся рассказать, вдруг для кого-нибудь не боян.
          Лежит мужик в больнице весь перебинтованный и диктует письмо: "Уважаемый автор! В Вашей книге "Как управлять вертолётом" на стр. 29 мною была обнаружена опечатка...".
    • 13 лет назад, # ^ |
      Rev. 4   Проголосовать: нравится 0 Проголосовать: не нравится

      Именно поэтому имеет смысл на любом контесте смотреть, какие ключи компиляции на сервере, и собирать локально точно так же - из командной строки. Конечно, если это возможно - и это одна из причин, за что я не люблю, например, OpenCup.
      Здесь нелишним ещё будет вспомнить, что в прошлом сезоне команда ИТМО, которая чемпион мира, не прошла на финал из-за Compilation Error, вызванного вы уже понимаете чем.
  • 13 лет назад, # ^ |
      Проголосовать: нравится +10 Проголосовать: не нравится
    А вы всегда уверены, что пишете код, который одинаково выполняется при любом наборе флагов визуалки?
    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Да я вообще на С++ не пишу, однако про точки следования в курсе. Но тут же речь не о каком-то хитром выверте компилятора - а о самом элементарном базовом понятии языка.
13 лет назад, # |
Rev. 2   Проголосовать: нравится +24 Проголосовать: не нравится

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

Либо накатить на компилятор Visual Studio нужные настройки (сложный способ), либо просто раздать командам батники компиляции, которые мы использовали (простой способ). Увы, задним умом все сильны :( Наша вина в ситуации есть - приношу команде свои извинения.
  • 13 лет назад, # ^ |
      Проголосовать: нравится 0 Проголосовать: не нравится

    Хорошо, хоть, что разобрались, откуда такая магия получилась :). Спасибо за быструю реакцию и помощь

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

    • 13 лет назад, # ^ |
        Проголосовать: нравится 0 Проголосовать: не нравится
      Еще в Новосибе было тестирование на сервере в течение всего контеста. Можно было отправить и получить какую-то циферку, показывающую работоспособность.
      • 13 лет назад, # ^ |
          Проголосовать: нравится 0 Проголосовать: не нравится
        Да, да, а из-за того, что эта циферка всё-таки вычислялась иначе, чем итоговая, можно было ещё и большую часть тура со своим реальным девятым местом повисеть аж на первом.
13 лет назад, # |
  Проголосовать: нравится +5 Проголосовать: не нравится
"Ведь мы знали, что, например, в случае Func(a(), b()); сначала вызовется именно b(), а только потом a()."

Вот это вы зря знаете.
  • 13 лет назад, # ^ |
    Rev. 2   Проголосовать: нравится 0 Проголосовать: не нравится

    Присоединяюсь, укажу только ещё источник: C++ Standard, section 5.2.2 p.8
    The order of evaluation of arguments is unspecified. All side effects of argument expression evaluations take effect before the function is entered. The order of evaluation of the postfix expression and the argument expression list is unspecified.

    Насчёт первоначального вопроса, то всё в порядке - оператор "+" не вводит т.н. sequence point в выражение. Поэтому в выражениях вида f(a)+g(a), порядок вычисления операндов не дефинирован, хотя сама операция сложения выполняется слева направо.