понедельник, 5 июля 2010 г.

[prog.flame] Вот за что мне не нравится C++

…так это за “хрупкость” результатов компиляции и линковки. Приведу пару примеров, с которыми довелось столкнуться в конце прошлой недели.

В первом эпизоде довелось поучаствовать самому. Подошел ко мне коллега, который пожаловался, что пару дней долбется с проблемой возникновения Access Violation буквально на ровном месте. Он занимался доработкой чужой программы, изменил одну из структур и в месте, которое раньше прекрасно работало, стал выскакивать злобный AV. Причем отладчик указывал в какие-то дебри реализации STL-я и понять, почему мы туда попали не представлялось возможным.

Когда я покурил проблемные исходники, мне стало понятно, что в самом месте возникновения AV проблемы нет, там нормальный корректный код. Значит, раз ошибка таки возникает, эта ошибка является наведенной – где-то в другом месте происходит “расстрел” памяти, а затем здесь его последствия “аукаются”.

С не самым лучшим настроением, предвкушая длительное бодание с чужим кодом я сделал себе checkout исходников из репозитория, скомпилировал, запустил… И ошибки не возникло.

Мой коллега пользовался Visual Studio, в том числе и для компиляции. Полного cleanup-а проекта он давно не делал. Что там в результате многих компиляций и инкрементальной линковки получалось – фиг знает. В конце-концов что-то срослось не так и на ровном месте возникал AV. Зато полный cleanup с последующим полным build-ом устранил проблему совсем.

Сам-то я еще в середине 90-х выработал для себя простое правило – если в большом C++ проекте начинают выскакивать непонятные глюки, самое первое, что нужно сделать – выполнить полный rebuild (т.е. сначала хардкорный cleanup, затем build с “чистого листа”). Не буду утверждать, что это помогало в 50% случаев, но помогало часто.

О втором эпизоде я прочитал в блоге Стива Хьюстона (одного из главных разработчиков библиотеки ACE и C++ной реализации AMQP в Apache-вском проекте). Перескажу вкратце: человек убил 5 часов рабочего времени на поиск проблемы с использованием ACE_INET_Addr. Оказалось, что сама библиотека ACE компилировалась с опцией ACE_HAS_IP6 и в ее представлении структуры ACE_INET_Addr было отведено место для sockaddr_in6, а вот приложение – без опции ACE_HAS_IP6, соответственно, в ее представлении структуры ACE_INET_Addr структуры sockaddr_in6 не было. Понятное дело, что когда экземпляр ACE_INET_Addr попадал из кода приложения в код ACE, приходил маленький пушной зверек.

Когда я был молодым, полным сил и задора, мне казалось, что все это херня. Что в правильных руках и с правильными инструментами ничего подобного не происходит, а С++ – самый лучший язык программирования :/

Теперь же, когда я узнаю, что мы теряем дни в поисках подобных проблем, остается только произнести про себя какую-нибудь банальность, вроде шит хаппенс или се-ля-ви. Ну и в очередной раз вспомнить, какой объем унаследованного C++ кода находится в нашем распоряжении, что бы очередной позыв переписать все на какой-нибудь Scala задохнулся в зародыше :(

В качестве же сухого остатка могу рассказать о простых вещах, которые я использую, чтобы не сталкиваться с подобными проблемами или минимизировать их эффект:

  • простой и надежный способ компиляции C++ных проектов. Всякие интеллектуальные инкрементальные компиляции и линковки – в топку. Лучше потерять несколько минут ожидая завершения компиляции, чем тратить часы на поиск несуществующих проблем;
  • компиляция всех сторонних библиотек вместе со своим проектом. Чтобы избежать проблем различий в настройках компилятора. Еще очень желательно, чтобы система компиляции могла проверять согласованность параметров, а так же могла распространять обязательные параметры одного подпроекта на все использующие его компоненты. Отчасти Mxx_ru это умеет, поэтому-то я Mxx_ru в свое время написал и использую его каждый день;
  • я компилирую сразу в release-режиме. Отлаживаюсь только с помощью отладочных печатей и тщательного обдумывания происходящего. Только в особо злостных случаях делаю полный rebuild в debug-режиме, чтобы воспользоваться отладчиком. После чего опять полный rebuild в release-режиме.

16 комментариев:

  1. дык, а фигли делать то :) тем более если с приплюснутого не слезть.

    ОтветитьУдалить
  2. дык, а фигли делать то :)

    Ну, в идеале, было бы лучше перейти на какой-то более строгий и надежный язык :)

    тем более если с приплюснутого не слезть.

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

    Да и еще -- лучше всего работать на C++ в маленьких командах. В больших неизбежно окажутся ламеры, которые будут с энтузиазмом разбрасывать тут и там мины замедленного действия.

    ОтветитьУдалить
  3. Оказывается, меня уже обсуждают: http://board.rt.mipt.ru/?read=6549893

    Приятно узнавать о себе столько нового :) Например, что за 16 лет профессионального программирования я написал порядка 100k строк кода :)))

    100k строк -- это приблизительный объем моих публичных, OpenSource проектов. Вообще же я пишу, в среднем, около 45-60k строк C++ного кода в год. Бывали годы и 70k, но сейчас уже меньше -- в районе 40k. Приходится руководить подчиненными, времени на программирование остается не так много.

    ОтветитьУдалить
  4. Ну, в идеале, было бы лучше перейти на какой-то более строгий и надежный язык :)

    На D :))
    Вон на rsdn практически все что предлагают для улучшения С++ (если отбросить заведомую чушь) http://www.rsdn.ru/forum/philosophy/3861001.flat.aspx#3861001 уже есть в том или ином виде на D :(

    ОтветитьУдалить
  5. Ну все теперь ты жабист :)

    ОтветитьУдалить
  6. 2Rustam:

    >На D :))

    Не, D не взлетит. Из нативных языков надежда теперь на Go :)

    >Ну все теперь ты жабист :)

    Не, все еще не так плохо :) Чем жабистом, так лучше скалистом (или скалолазом?) :)
    Хотя нужно еще посмотреть, что из Java 7 получится.

    ОтветитьУдалить
  7. >http://board.rt.mipt.ru/?read=6549893

    улыбнуло. все таки м/д в рунете катастрофическое большинство.

    >Ну, в идеале, было бы лучше перейти на какой-то более строгий и надежный язык :)
    С# :)? Или даже F#. Функциональщина сейчас в моде.
    >В больших неизбежно окажутся ламеры, которые будут с энтузиазмом разбрасывать тут и там мины замедленного действия.

    Эту мину заложил старина Бьерн. lol.

    ОтветитьУдалить
  8. 2mrbrooks:

    >>Ну, в идеале, было бы лучше перейти на какой-то более строгий и надежный язык :)
    >С# :)? Или даже F#. Функциональщина сейчас в моде.


    Я для себя сделал такой список:
    - если нужно делать Windows-only разработку, то C#;
    - если нужна кроссплатформенность, то Scala (хотя бы для первого proof-of-concept проекта).

    Для кроссплатформенного native ничего кроме C++ нет. Может быть со временем Go допилят и библиотеками/инструментами его снабдят. OCaml, имхо, не конкурент.

    В чистую функциональщину я не верю (не практично это, имхо).

    >Эту мину заложил старина Бьерн. lol.

    Бьерн сделал очень хороший шаг -- оснастил C более мощными и безопасными возможностями. К сожалению, никто не проделал того же самого с самим C++ лет 10-15 назад.

    ОтветитьУдалить
  9. Да гугль вполне может протолкнуть Go, но Go все-таки по моему скорее замена Си, а не замена C++.

    Кстати ява также как и C++ уперлась в свой потолок, но там хоть есть шанс заменить на новый язык под тот же байт код, но скорее не Scala (слишком она переусложнена мне кажется) а на некий аналог C#.

    ОтветитьУдалить
  10. Для кроссплатформенного native ничего кроме C++ нет. Может быть со временем Go допилят и библиотеками/инструментами его снабдят. OCaml, имхо, не конкурент.

    Ну если разбираться паскаль семейство все еще живо, и какой нибудь Free Pascal тоже вариант, хотя конечно уже сильно маргинален.
    OCaml да в мейнстрим не влезет уже. Но в смеси с Си/C++ вполне можно использовать. Тут как раз думал утилитку переписать с такой смеси на чистый C++ (для передачи исходников) начал и бросил, прямое тупое переписывание на C++ подняло потребление памяти в несколько раз, а там и так на грани было, 500 метров за раз спокойно съедало, начал разбираться оказались виноваты строки, GC камловский все что можно закешировал (там много кусочков одинаковых, пути к папкам) несмотря на то что строки в Ocaml mutable. Конечно на C++ можно сделать еще эффективней, но на это нужно времени больше (только на портирование) чем было потрачено на первоначальное написание на OCaml. Ну и еще остановило то что основной алгоритм реализованный как вариантный тип плюс десяток функций с ним работающих вырождался на C++ в целую иерархию классов, сразу такие ломы возникли что плюнул :)

    ОтветитьУдалить
  11. >Кстати ява также как и C++ уперлась в свой потолок, но там хоть есть шанс заменить на новый язык под тот же байт код, но скорее не Scala (слишком она переусложнена мне кажется) а на некий аналог C#.

    Да, у явы оказался очень небольшой запас по развитию.

    И на счет Scala согласен. Но Scala есть здесь и сейчас, его можно использовать -- есть книги, какое-то community, даже success story (что немаловажно при обосновании выбора Scala перед начальством). А вот аналога C# на JVM пока я что-то не вижу :(

    ОтветитьУдалить
  12. >Ну и еще остановило то что основной алгоритм реализованный как вариантный тип плюс десяток функций с ним работающих вырождался на C++ в целую иерархию классов, сразу такие ломы возникли что плюнул :)

    Ага, бывает такое. На языке с GC некоторые решения получаются настолько простыми и эффективными, что вручную на С++ забабахаешься переписывать.

    ОтветитьУдалить
  13. Тут даже не GC а использование алгебраического типа, грубо есть дерево хранит разнородные элементы и надо обходить его несколькими разными способами. В C++ да и в яве с шарпом просто нет адекватных конструкций, приходится или городить иерархию классов или мудрить с boost::variant. При этом код раздувается практически на порядок.

    ОтветитьУдалить
  14. >В C++ да и в яве с шарпом просто нет адекватных конструкций, приходится или городить иерархию классов или мудрить с boost::variant.

    И такое дело есть. Но мне чаще не хватает именно GC.

    ОтветитьУдалить
  15. Насчет Scala, читаю ту же http://fprog.ru/planet/ и вижу засилие Clojure, понятно что она на пике найповой волны, но все равно похоже более популярна.

    ОтветитьУдалить
  16. @Rustam: мне сложно оценивать, Scala ли более популярна, или Clojure (и у кого -- у программистов практиков или у журналистов/блоггеров). Шума, на мой взгляд, у Clojure больше. Но о Lisp-овых языках всегда больше говорят :)

    Для меня важно другое: lisp-подобный язык я не буду использовать на практике. Как и nemerle-подобный. Не должны прикладные программисты иметь средств для создания собственных подъязыков. Программирование и без того сложная штука.

    Так что для меня Clojure для Scala не конкурент.

    ОтветитьУдалить