15 сентября 2008 г.

Javascript: "option" object и IE

Простой скриптик. Берет все input-элементы с именем "hiddenlist" и заносит их в выпадающий список "selectlist". Отображаться в списке должно значение value, а на сервер отсылаться значение id. В Firefox работает, а в Internet Explorer 7 — нет.

var elems = document.getElementsByName("hiddenlist");

var selectobj = document.getElementById("selectlist");
removeAllChildren(selectobj);

for (i = 0; i < elems.length; ++i) {   var optobj = document.createElement("option");   optobj.value = elems[i].id;   optobj.text = elems[i].value;   selectobj.appendChild(optobj); }

Почему-то в IE после вызова appendObject(optobj) поле text очищается, или просто игнорируется. А поле optobj.value обрабатывается нормально. В результате в IE после вызова скрипта имею список из N пустых строк, но при этом на сервер отсылаются правильные значения.

Пришлось сделать так:

for (i = 0; i < elems.length; ++i) {   selectobj.appendChild(document.createElement("option"));   selectobj.options[i].value = elems[i].id;   selectobj.options[i].text = elems[i].value; }

В этом варианте работает в обоих браузерах. Мне, в общем-то, все равно, хотя второй вариант мне кажется менее приятным для глаз. Однако, непонятно почему первый вариант не работает в IE.

12 сентября 2008 г.

Exceptions и Visual C++

Недавно понадобилось собрать STL-проект на Visual C++ 2008 без использования исключений.

Почитал мануал по компилятору, долго игрался с настройками проекта и ключами /EHsc... Что-то никак не получается отключить исключения.

Наконец, залез в код STL и обнаружил там некий волшебный макрос _HAS_EXCEPTIONS. Добавление в опции компиляции ключа -D_HAS_EXCEPTIONS=0 привело к нужному результату — исключения в STL отключились.

Припоминаю, что лет шесть назад пытался решить эту же задачу для Visual C++ 6.0. И решалась она абсолютно таким же способом, через _HAS_EXCEPTIONS. Вот я и думаю, то ли Microsoft что-то перемудрил с Visual C++, то ли я его "готовить" не умею?..

С праздником вас, дорогие коллеги! С днем программиста! :)

Updated: Если нужно линковаться с msvcrt.lib или msvcrtd.lib (опции компилятора /MD или /MDd), то нужно определить еще один макрос:
#define _STATIC_CPPLIB

Updated: Вот интересная статья в тему http://ccollomb.free.fr/blog/?p=34

2 сентября 2008 г.

Задачи на собеседованиях: графы

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

У нас есть ориентированный граф и его матрица смежности. Необходимо разработать алгоритм, вычисляющий количество узлов графа, участвующих в циклах. Речь идет просто о количестве узлов. Нас не интересуют ни количество циклов, ни количество узлов в каждом цикле. Только общее количество узлов, задействованных в циклах данного графа.

UDP: Кстати, найдя кол-во узлов мы сможем ответить на общий вопрос о наличии цикла в графе. Иногда вопрос звучит именно так.

3 июля 2008 г.

Как сменить shell

Тема не новая, но вот сегодня опять занимался решением старых проблем...

Не знаю кому как, но лично я считаю, что лучший shell — это bash. Не буду перечислять все преимущества, но одно просто неоспоримое — привык я к нему :) Я вообще люблю консоль, но bash — особенно. Он какой-то уютный, что ли.

Но вот сегодня захожу на очередной удаленный сервер по ssh и попадаю в ненавистный мне tcsh. Всё б ничего и можно потерпеть, но хочется уюта :) Но я здесь не root, и мне особо не рады. Что делать?

Исправляется довольно просто: нужно прописать смену shell в настроечном файле, который считывается при логине. Для tcsh этот файл называется .login и находиться должен в моей домашней папке. Если его нет, то нужно создать и прописать в нем следующее:

if (-f /bin/bash) then
  setenv SHELL /bin/bash
  exec /bin/bash --login
end if

Эти строки заменяют существующий shell, загружаемый по умолчанию, на bash. В данном случае по умолчанию был установлен tcsh.

В случае другого shell решение немного изменится. Например, если по умолчанию загружается sh, то никакой .login не поможет — sh просто не знает о существовании такого файла. Для sh нужно исправлять файл .profile (он тоже должен находиться в домашней папке):

if [ $SHELL = "/bin/sh" ]; then
  if [ -f /usr/bin/bash ]; then
    SHELL=/usr/bin/bash
    export SHELL
    exec /usr/bin/bash --login
  fi
fi

Маленький нюанс здесь — bash тоже считывает .profile и двойной if нужен здесь для того, чтобы исключить вероятность бесконечной рекурсии при загрузке bash.

Полный список соответствия разных shell'ов и настроечных файлов можно почитать здесь.

16 июня 2008 г.

Блокирующие вызовы и ошибка EINTR

Недавно столкнулся с ошибкой, связанной с неправильной обработкой возвращаемого значения функции sem_wait().

Функция sem_wait() уменьшает текущее значение семафора на 1. Если прежнее значение семафора было 0, то поток, выполняющий функцию sem_wait, блокируется до изменения значения семафора. При успешном выполнении — функция возвращает 0; при ошибке — вернет -1, а errno будет содержать код ошибки.

Вот пример неправильной обработки возвращаемого значения:

int res = sem_wait(&sem);
if (0 != res)
{
  // Ошибка!
  // Сообщаем об ошибке и выходим.
  ...
  return;
}

// Ошибки нет - продолжаем работу.
...


Что здесь неправильно? Дело в том, что функция sem_wait() является блокирующим системным вызовом. Поэтому выполнение этой функции может быть прервано, если выполняющийся поток получит какой-нибудь сигнал (например, сигнал о завершении другого потока). В этом случае функция вернет -1, а errno будет равна EINTR.

При этом никакой ошибки на самом деле нет, это просто способ дать программе обработать пришедший сигнал. Это необходимо учитывать, и вызов sem_wait() лучше всего делать в цикле. Например, таком:

int res;
do
{
  res = sem_wait(&sem);
}
while (0 != res && EINTR == errno);

if (0 != res)
{
  // Ошибка!
  // Сообщаем об ошибке и выходим.
  ...
  return;
}

// Ошибки нет - продолжаем работу.
...

Это касается и других блокирующих функций. Например, select, wait, waitpid и др. Возможность прерывания функции с ошибкой EINTR обычно описана в man'е.

20 мая 2008 г.

Ошибки кодирования

Очередной багфиксинг показал, что за 30 лет мало что изменилось :)

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

И вот, модуль логгирования вдруг стал время от времени "падать" в совершенно непредсказуемые моменты. Оказалось, что виноват в этом один из компонентов, который вместо осмысленного текста посылает в модуль логгирования мусор. У компонента проблема, но почему модуль-то падает?..

Ошибка стара, как Си :) Разработчик модуля логгирования реализовал функцию примерно так:

void logMessage (const char *str)
{
  printf(str);
}

Видно ошибку? Проблема тут в том, что текст сообщения напрямую подается в функцию printf, минуя использование форматных строк типа "%s\n". Если подавать на вход функции осмысленный текст, то все будет хорошо, до тех пор пока в тексте сообщения не встретится "%s". В этом случае функция printf попытается сформировать новую строку, предполагая, что в стеке находятся дополнителльные аргументы. А аргументов-то там и нет. Вот тут-то printf и упадет с большой долей вероятности (скорее всего это произойдет, когда printf попытается вызвать strlen для значения, которое достанет из стека).

В нашем случае на вход функции подавался случайный мусор, поэтому время от времени в нем встречалась последовательность "%s", и модуль вываливался с ошибкой Segmentation fault.

Вот правильное использование printf для нашей функции логгирования:

void logMessage (const char *str)
{
  printf("%s", str);
}

Вызов printf и ей подобных должен выглядеть именно так и никак иначе.

Читать об уязвимостях printf.

Технологии мчатся вперед, а ошибки не меняются :)

16 марта 2008 г.

Проблема импорта в Mozilla Thunderbird

Столкнулся сегодня с проблемой импорта почтовых сообщений из Microsoft Outlook в Mozilla Thunderbird 2.0.0.12.

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

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

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

18 февраля 2008 г.

Цитата: Хаак

Бывший MVP (Most Valuable Professional – Самый Ценный Специалист) Microsoft Фил Хаак о повторных использованиях и обобщениях при разработке ПО
«Избегайте преждевременного обобщения», советует Хаак. «Не создавайте систему способную предсказать любое изменение. Пусть она будет достаточно гибкой для внесения изменений».

Чтобы определить, когда требуется обобщение, Хаак использует Правило Трех: «Когда вы в первый раз увидите, что что-либо может повторяться, не обобщайте это. В следующий раз сделайте все аналогичным способом, возможно даже при помощи копипастинга, но только не обобщайте. В третий раз хорошенько подумайте, как это можно обобщить».

Updated: Знакомство с Vim

По просьбе читателей поправил текст старой статьи "Знакомство с Vim. Настройки". Добавлено краткое описание режимов, а сам контент сделан более последовательным.

6 февраля 2008 г.

Еще раз об MDA

Возвращаясь к напечатанному, хотелось бы добавить...

Разработка серьезного программного продукта обычно ведется с использованием средств версионного контроля. При таком подходе довольно часто возникает необходимость выполнять какие-то работы (например, исправление ошибок) на дополнительных ветках, чтобы по окончании этих работ аккуратно слить результат на основную ветку.

Так вот. Инструмента для выполнения merge UML-моделей я еще не встречал. В результате, все слияния и разрешения конфликтов выполняются визуально и вручную. Слияние элементарного изменения может потребовать в разы (а то и в десятки раз) больше времени, чем само внесение этого изменения в модель. Поэтому, я не понимаю, как можно нескольким разработчикам эффективно работать по MDA-технологии.