Всем доброго времени суток. Сегодня я хочу рассказать вам одну очень поучительную и, увы, немного печальную историю. Я думаю, что всем, кто пишет на C++, будет полезно прочитать.
Итак, всего-то неделю назад мы вернулись с Чемпионата Урала. Как вы могли прочитать в письмах и обзорах, на чемпионате было два тура: игровой и основной. И если с основным все понятно, то с игровым все вышло весьма презабавно. Началась эта история с того, что, когда на подведении итогов тура показали результаты швейцарки, то мы не увидели себя в первых ~30 командах, которые поместились на основной монитор. Хотя наш бот был не слишком умный, но все же трудно было предположить, что все написали лучше. Мысль о том, что он где-то упал, отпала, т.к. и локально, и на сервере компилятор был одинаковый, а, значит, никаких "трудностей перевода". Ну да всякое бывает, на следующий день был основной тур, и мы забили.
Пару дней назад появились результаты. Увидев нашего бота на предпоследнем месте, удивлению нашему не было предела, т.к., если верить монитору, то он проиграл даже SampleBot'у, которого раздали всем командам. Это было просто невероятно, мы все-таки догадались против него поиграть. Опять же, падение почти исключалось, т.к. у кого-то он смог выиграть, т.е. хотя бы ход сделать успевал. Естественно мы скачали исходник, скомпилили, запустили и... Он выигрывал! Невероятно, но это был "тот самый" наш, самый что ни на есть разумный бот! Выиграв у команды N из топ-5(!!!), я решил наконец-то посмотреть, что же не так. К счастью, доступны были и логи проведенных на сервере матчей. Что же я увидел? Вместо нашей программы кто-то тупо складывал все шарики в левый столбец и через 16 ходов проигрывал. Естественно, мы подумали, что это какая-то нелепая ошибка и сразу написали организаторам.
Как вы думаете, мог ли ответ удивить нас еще сильнее, чем все произошедшее? Да, ответ буквально поверг в шок. Оказывается, тестирование на сервере проходило не совсем как локально, а именно, флаг /GL, который в студии в релизе включен по умолчанию, был отключен на сервере, и, утверждалось, что именно это было причиной столь странного поведения. Действительно, отключив этот флаг у себя, я получил то же, что и в том самом печальном логе. Кстати, в дебаге этот флаг тоже отключен (и включить его нельзя), однако она работала нормально. День прошел в поиске ошибки, стоит ли говорить, сколько вариантов ошибок я пытался найти?
Оказалось все довольно прозаично: наша программа делала перебор на два хода, соответственно, делала ход один раз, изменяя при этом игровое поле, потом ходила еще раз. Вот строчка: int score = FirstMove(tmp) + SecondMove(tmp); tmp - игровое поле, в функции передавалось по ссылке. Кажется, что все логично, но не тут-то было! Опытным путем было установлено, что в релизе в зависимости от этого пресловутого ключика (/GL) зависит порядок вызова функций. Т.е. у нас всегда функции вызывались в правильном (для людей, пишущих слева направо) порядке, а на сервере - в обратном. Разумеется, как только я разнес эту конструкцию на две операции, все стало работать. Вот такая вот подстава.
На самом деле, это не совсем непредсказуемая подстава. Ведь мы знали, что, например, в случае Func(a(), b()); сначала вызовется именно b(), а только потом a(). То есть такой код уже потенциально опасен. Засада в том, что программа работает правильно именно в случае по умолчанию, а падает в не совсем обычной ситуации. Тем не менее, это послужит хорошим уроком. Может быть, получить такую проблему при разработке большого проекта - зло еще большее, чем потеря приза.
Оптимизатор - очень хитрая штука, он совершенно изменяет вашу программу. Как машина, он предельно логичен, и нельзя с уверенностью сказать, баг ли это, или специфика машинной логики. К сожалению, он в любом случае не знает, какой логикой вы руководствуетесь при написании кода. Поэтому старайтесь использовать в своем коде наиболее "простые" конструкции, которые не могут иметь двоякого толкования.
Кроме того, выскажу свое пожелание организаторам игровых туров. Понятно, что проводить полное тестирование при каждой сдаче невозможно, да и не нужно. Но вполне можно было протестировать решение участников хотя бы на Sample-Bot'е. Знания вердикта, времени, памяти, количества ходов и рандсида было бы с высокой вероятностью достаточно для понимания того, работает ли программа на сервере так же, как и локально. Понятно, что такой случай, как у нас, было сложно предположить, но это пожелание на будущее.
Я не сказал сказал, что они что-то должны. Разве вам кажется глупостью проверять решение на одном боте? Уверен, что многие иного мнения, минимальная проверка работоспособности программы в реальных условиях точно не мешает. Кроме того, в Новосибирске так почему-то делают.
Как правило, проблемы со стандартом проявляются при использовании разных компиляторов на сервере и на рабочей машине. Этот пост о том, что это не всегда так. Уверен, для кого-то он точно окажется полезен.
Тут и возникают непонятнки.
Неочевидно же с ходу, зачем компилятору может потребоваться менять порядок вызова функций в строке?
Но вообще да, всё плохо вроде было с STL и с 64-битными числами.
> Если современные версии (2005 и 2008)
...
Идея в том, что ни одной книге нельзя безоговорочно верить. Стоит помнить, что повсюду вражеские шпионы!
Лежит мужик в больнице весь перебинтованный и диктует письмо: "Уважаемый автор! В Вашей книге "Как управлять вертолётом" на стр. 29 мною была обнаружена опечатка...".
Здесь нелишним ещё будет вспомнить, что в прошлом сезоне команда ИТМО, которая чемпион мира, не прошла на финал из-за Compilation Error, вызванного вы уже понимаете чем.
Либо накатить на компилятор Visual Studio нужные настройки (сложный способ), либо просто раздать командам батники компиляции, которые мы использовали (простой способ). Увы, задним умом все сильны :( Наша вина в ситуации есть - приношу команде свои извинения.
Хорошо, хоть, что разобрались, откуда такая магия получилась :). Спасибо за быструю реакцию и помощь
А на будущее - хоть правила и оговорены, но т.к. весь тур зависит от одной программы, можно неявно идти навстречу участникам. Я помню как в Новосибирске организаторами были просмотрены все "неадекватные" решения, и хотя это не оговорено правилами, они заменяли посылки (т.е. последнюю на предпоследнюю - ошибались люди за последние 5 минут).
Вот это вы зря знаете.