Архив 31 марта 2011

Про memcpy, memmove и Open Source

Кто не читал книжку Кернигана и Ритчи – сразу может скроллить до следующего поста. Остальные насладятся замечательной историей, почерпнутой у [info]avva.

В общем, так исторически сложилось, что в языке программирования Си есть две функции, которые копируют произвольные области памяти. Одна называется memmove – memory move, другая – memcpy, memory copy. Они имеют одинаковый синтаксис, но работают немного по-разному. Первая правильно копирует данные, когда области перекрываются, вторая – нет, но работает “чуть-чуть” быстрее. Естественно, что программисты эти функцию путают, и один из авторов языка Си, Брайан Керниган в своей книге “Практика программирования” даже высказался на этот счет:

В стандарте ANSI C определены две функции: memcpy, которая работает быстрее, но может затереть память, если источник и приемник данных пересекаются, и memmove, которая может работать медленнее, но всегда корректна. Бремя выбора между скоростью и корректностью не должно взваливаться на программиста; должна быть только одна функция. Считайте, что это так и есть, и всегда используйте memmove.

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

В общем, разработчики одной из наиболее распространенных реализаций этих функций – библиотеки glibc, использующейся практически во всех Linux-образных операционках, решили, что хорошо бы немного доработать memcpy, чтобы та работала еще быстрее, при этом сделав так, что она совсем перестала работать с перекрывающимися областями в памяти. Эта версия библиотеки прошлым летом была включена в дистрибутив Fedora – и вскоре обнаружилось, что некоторые программы перестали работать. Баг искали несколько месяцев, пока не поняли, что дело в том, что авторы этих программ понадеялись на то, что memcpy всегда будет правильно работать в тех самых “неправильных” случаях.

После этого началась веселуха. Авторы glibc, и “по совместительству” – разработчики Fedora – заявили, что никакого бага нет, а во всем виноваты криворукие программисты. Линус Торвальдс (живой бог для всех линуксоидов) пишет примерно следующее:

You can call it “crap software” all you like, but the thing is, if memcpy doesn’t warn about overlaps, there’s no test coverage, and in that case even well-designed software will have bugs.

Then the question becomes one of “Why break it?”

и

Quite frankly, I find your attitude to be annoying and downright stupid.

How hard can it be to understand the following simple sentence:

THE USER DOESN’T CARE.

Если у пользователя после обновления ОС не работает кривой Flash-плагин – то пользователь не будет винить в этом криворуких разработчиков плагина, а будет винить разработчиков ОС. Разработчики glibc этого не понимают и их главарь, Ульрих Дреппнер, подводит вот такой итог под всей этой дискуссией:

The existence of code written by people who should never have been allowed to touch a keyboard cannot be allowed to prevent a correct implementation.

Как же это не похоже на ту идеологию, которую проповедует Raymond Chen из Microsoft. Но вся эта история объясняет, почему на большинстве пользовательских ПК все еще стоит Windows.

PS В итоге до сих пор в glibc остается новая версия memcpy. Обнаружилось, что в огромном количестве программ программисты полагались на недокументированное поведение этой функции, где-то исправили, а где-то – нет.

Скачиваем книги с ResLib.com

Понадобилась мне сегодня одна книжка по математике. Но вот беда – ее не оказалось в той библиотеке научно-технической литературы, которую на 35 дисках предлагают скачать на РуТрекере. Лет пять назад я в таких случаях пользовался “Электронной библиотекой мехмата МГУ”, благо в alma mater на 15 этаже висела бумажка “сайт lib.mexmat.ru, логин mexmat, пароль mexmat”. Но что знают двое – знает и свинья, поэтому эти логин с паролем распространились по всему интернету, попали в лапы правообладателей и те потребовали прикрыть лавочку, написав чуть ли не Лупанову (тогдашний декан мехмата) с Садовничим (а это ректор МГУ). Библиотека, естественно, закрыла доступ извне полностью, оставив его только для сети мехмата.

Тем не менее, через несколько лет от ЭБММ отпочковалась новая электробиблиотека ResLib.com – те же книги, но с доступом через Web и ограничением – не более 25 страниц за раз. На мое счастье, нужная мне книжка в ней нашлась. Но вот предложение читать по странице в полчаса, да еще и через неудобный Web-интерфейс мне показалось оскорбительным.

Я решил попробовать скачать эту книжку – есть у меня еще нехорошая привычка не доверять веб-сервисам. Мало ли – через неделю все закроется? Так что, посмотрев на код картинок, которые показываются при просмотре книг в этой библиотеке, я решил запустить свой любимый ReGet и воспользоваться в нем прекрасной опцией “создать нумерованный список”. URL картинок выглядел так:

http://reslib.com/img2.php?b=635101403&s=l&p=1

Параметры обозначают следующее: b – идентификатор книги, s – размер картинки, нам нужен l, то есть large, p – номер страницы (видимо, “выдираются” они из djvu-файла). Запускаем ReGet и создаем нумерованный список – это замечательная фича, разработчиками упоминаемая, как “уникальная”.

reget-spisok

Запускаем закачки и… в директории, куда мы хотим скачать файлы, появляется файл img2.php (правда, Windows Explorer распознает в его содержимом png-картинку), а Регет бомбардирует нас несколькими десятками сообщений “файл уже существует, переименовать?”. Непорядок. И оказывается, что в одном из лучших download-менеджеров нет никакого способа дать свои названия куче файлов, закачиваемых автоматически.

Правда, я все же обнаружил в ReGet так называемые “макросы”, которые можно использовать для относительно массовых переименований закачек. Оказывается, что если объденить все файлы этой книжки в одну группу, то можно уже в группе задать правило, применяемое к тем файлам, для которых файл с тем же именем уже существует. Там есть выбор из нескольких настроек – “Спросить”, “Переписать”, “Переименовать” – и в случае переименования можно воспользоваться макросами. Обращу внимание почтенной публики на то, что в справке ReGet список макросов немного шире – в частности, имеется макрос Param, выдирающий из GET-запроса значение параметра с заданным именем. Как раз то, что надо – давайте назовем наши файлы page1.png, page2.png и так далее! Идем в свойства группы и ставим там такие настройки:

reget-makros

Теперь снова запускаем закачку, скачиваем пару десятков файлов уже с правильными названиями и на дальнейшие запросы получаем от сервера отлуп – “Ошибка 503, приходите позже”. Ну я думаю, вы поняли? Сервер пытается “забанить” нас по IP-адресу. Меняем айпишник (кто пользуется proxy, кто “пересовывает” соединение с провайдером) и продолжаем. Итого – за 15 минут скачиваем книжку целиком.

Зачем я это рассказываю? Разумеется, не с целью ограбить онлайн-библиотеку – как вы видите, это может сделать каждый. Я хочу продемонстрировать, что даже в лучших программах “из мира Windows” какие-нибудь не очень тривиальные действия сопряжены с возникающими “из ниоткуда” трудностями. Windows-стиль не предполагает работу с командной строкой, а уж тем более – написание сложных скриптов на командном языке. А я более чем уверен, что на любом языке какого-нибудь Unix shell (или даже Windows Power Shell, или даже в обычном cmd.exe) написание скрипта, скачивающего ту же кучу файлов по маске, займет те же 15 минут, что я изучал документацию ReGet и придумывал способы обхода непонятно откуда возникших ограничений.

Да, пользоваться wget намного (или немного :) ) сложнее, чем ReGet или Download Master – но первый можно использовать в каких-то своих целях, а “качалки” из Windows – только в рамках того, что предусмотрели их авторы.