Архив автора

Реализация cond на операторе ?:

Если кто будет спрашивать, cond — это из мира Лиспа.

Подобную же конструкцию можно сделать и в C:

var = (cond1 ? expression1 :
       cond2 ? expression2 :
       /* ... */
       else-expression);

Переменной var присваивается значение выражения expression1, если истинно значение выражения cond1, присваивается значение expression2, если истинно значение cond2, и т.д. Если ни один condX не истинен, присваивается значение else-expression.

По-моему, красиво. Для пущего сходства с Лиспом можно добавить скобочек.

УжасноПлохоНормальноХорошоОтлично (1 голосов, средний: 3,00 из 5)
Loading ... Loading ...

Исходники: текст или что-то другое?

Последние две недели мне приходилось разбираться в чужом коде с целью отловить и уничтожить неведомую ошибку. Не сказать, чтобы код был особенно сложен, но сам я настолько далек от звания гуру современного программирования, что несколько седых волос где-то в глубинах моей шевелюры наверняка за это время зародились. Задача усугублялось невозможностью выполнить код под отладчиком (это был планировщик ОС), так что в моем распоряжении были только

  • заботливо раскиданная по коду отладочная печать на терминал и
  • пресловутый «долгий пристальный взгляд».

Были еще встроенные в ОС средства протоколирования, но они именно в эти две недели не работали. Удивительное совпадение. Хотя нет, скорее, все дело в кривизне рук. Неважно.

Так вот, отладочная печать прекрасно себя зарекомендовала. Изучение длинных простыней протоколов из строк вроде «(kernDispatchThread) pre-switch ct=174380000 diff=55489 nn=4», «test 4»и даже просто «hahaha!!!» позволяют получить истинное удовольствие от расследования а-ля Хаус. Ну-ка, кто у нас тут вытеснил этот поток? Но вот аномальная последовательность строк найдена, и теперь никуда не денешься — придется разглядывать код, который эту аномалию продемонстрировал.

Казалось бы, самое время сказать: «Трепещите, о презренные байты! Склонитесь перед интеллектом Человека!». Но нет, байты трепетать отказываются, функции плодятся прямо на глазах, макросы-сволочи делают вид, что они функции, или даже того хуже — оперируют именами локальных переменных (ужас-ужас!). Вся эта компания разбегается по двум десяткам файлов, откуда, ехидно посмеиваясь, гадит в глобальные переменные, норовя смешать физическую и виртуальную адресацию.

Вот как-то так я и провел две недели.

И не переставало мне думаться, что текст в качестве носителья исходного кода всем хорош: и редактировать его легко, и всякие diff’ы со слияниями делать, и от платформы не зависит (ну, почти), да и сколько уже проверенных временем алгоритмов на строках есть. Вот только читать чужие исходники неудобно. Хорошая IDE позволяет от вызова функции перейти к ее определению, но серебряной пулей тут и не пахнет. Пока читаешь код вызванной функции, забываешь, что делала вызывающая. И хорошо еще когда в когнитивном процессе участвует две функции. А что если 10? Или 50?

В таком случае поможет только абстрагирование: надо поделить 50 функций на 5 групп, рассмотреть взаимодействие этих групп, а потом — внутри каждой в отдельности. Да, модульное программирование явно неглупые люди придумали. Вот только выделение абстракций — отдельная большая работа. Если мы читаем свой код, то эта работа уже сделана еще при его написании. А вот выделение абстракций на уже существующем коде… Вот тут-то его текстовая природа и подкладывает нам козу: чтобы выделить абстракции, нужно сначала код прочитать и понять.

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

В таком подходе есть ряд очевидных недостатков. Например, нужен гигантский монитор. И отсутствие аллергии на работу мышкой. И ширина строк кода должна быть вменяемой (обратили внимание, какие они короткие в демке?). Но в целом идея здравая: кто сказал, что код — это обязательно плоский текст? (Я не говорил.) При анализе чужого кода такая штука уж точно будет полезной.

P.S. Я давно уже дочитал «Организацию ЭВМ» и с пяток книжек сверху. Просто под линуксом не работает мой антикварный сканер. Постараюсь что-нибудь придумать.

УжасноПлохоНормальноХорошоОтлично (Еще не оценили)
Loading ... Loading ...

Переменное число аргументов в функциях C

Полезная, но редко используемая и часто недопонимаемая возможность языка C — переменное число аргументов функции. Это такая особая сишная магия, позволяющая писать функции наподобие printf(). Обычно такие функции пишутся именно для форматированного вывода текста, хотя возможны и другие применения. В учебниках любят приводить в качестве примера функцию сложения произвольного числа аргументов, но вряд ли такое можно увидеть в реальном коде.

Выглядеть определение такой безразмерной функции может как-то так (три точки в списке формальных аргументов следует воспринимать буквально, то есть там именно три точки):

int myfunct(int arg1, double arg2, ...) { /* какой-то код */ }

Так как в языке C обычно придерживаются соглашения о вызовах cdecl, то аргументы, передаваемые функции, передаются через стек, причем кладутся туда, начиная с последнего (правого) аргумента и заканчивая первым. Вот вам примерный портрет стекового фрейма для вызова функции с N аргументами на процессоре x86:

Стековый фрейм

Все наши знания о фрейме ограничены значениями регистров ESP и EBP. Та точка отсчета, от которой мы можем отталкиваться для доступа к аргументам — текущее значение регистра EBP. Например, первый аргумент лежит по адресу EBP+8, а второй — по адресу EBP+12. Ладно, а сколько нужно прибавить к EBP, чтобы получить адрес N-го аргумента в стеке? Для этого нужно знать:

  • сколько всего передано аргументов;
  • размеры аргументов (кто сказал, что они должны быть одинаковы?).

Ни то, ни другое в доступных нам ESP и EBP не хранится. Знаем мы только то, что именованные аргументы имеют меньшие адреса. Выходит, что размеры аргументов должны быть жестко определены в теле функции или же получены на основе значений именованных аргументов. Но как определить их количество? Рассчитывать на форматную строку, как в printf? Но кто гарантирует, что эта строка правильная?

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

Правила нехитрые:

  • функция должна иметь по крайней мере один именованный параметр (именованные идут в списке формальных параметров перед неименованными);
  • перед доступом к аргументам нужно вызвать va_start, чтобы инициализировать объект контекста (va_list);
  • используем va_arg для последовательного доступа к неименованным аргументам;
  • по окончанию вызываем va_end в той же функции, что и va_start.

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

#include <stdio.h>
#include <stdarg.h>
int sum(int count, ...) {
    va_list args;
    int total = 0;
    va_start(args, count);
    while (count--)
        total += va_arg(args, int);
    va_end(args);
    return total;
}
int main() {
    printf("%d\n", sum(1, 33));
    printf("%d\n", sum(4, 1, 2, 3, 4));
    return 0;
}

Пояснения здесь излишни. Кстати, va_list можно передавать в качестве аргумента другой функции. Надо только не забыть вызвать для него va_end в той же функции (а точнее, в том же вызове функции), где был вызван va_start. Не знаю точно, что делает va_end, но если он манипулирует значением EBP, то лучше бы это значение было тем же, что и раньше.

Зачем может понадобиться передача va_list? Обычно для написания оберток для функций семейства printf, например, в каком-нибудь логгере:

int msgOut(enum msgLevel level, const char *fmt, ...) {
    va_list ap;
    int ret;
    va_start(ap, fmt);
    ret = msgOutL(level, fmt, ap);
    va_end(ap);
    return ret;
}
int msgOutL(enum msgLevel level, const char *fmt, va_list vl) {
    if (level >= s_level)
        return vfprintf(s_stream ? s_stream : stdout, fmt, vl);
    else
        return 0;
}

Напоследок хочется сказать вот что. Если вы пишете функцию с переменным числом аргументов не от нечего делать, а в реальном проекте, то использование stdarg.h — единственный вариант. Организация стека в x86, описанная выше, — это штука, на постоянство которой не стоит рассчитывать. Оптимизирующие компиляторы могут организовать передачу аргументов через регистры, а уж про перенос программы на другие платформы и говорить не приходится. Еще одна рекомендация — пишите такие функции только для форматирования текста. Другие применения обычно создают больше проблем, чем решают.

УжасноПлохоНормальноХорошоОтлично (3 голосов, средний: 3,33 из 5)
Loading ... Loading ...

Наши инструменты

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

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

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

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

Не нравится пример с машиной? Те же соображения верны для игры на пианино, занятий виндсерфингом, боевых искусств…

Недавно я стал ощущать примерно то же самое при программировании, после того как полностью перешел на Emacs в качестве замены всех IDE. Да, непросто запомнить комбинацию из трех-четырех, а то и более клавиш. Часто для нужных действий нет сочетаний клавиш, а иногда нет даже и встроенной возможности выполнять эти действия. Тогда я трачу кучу времени, ищу в Сети дополнения, правлю их напильником, вешаю некоторые действия на клавиши, потом несколько дней к ним привыкаю — и все. Больше я об этих действиях не думаю. Они выполняются сами, без моего участия.

Например, чтобы увеличить ширину текущего окна на 20 символов, я нажимаю Ctrl-u 2 0 Ctrl-x Shift-]. В общей сложности 8 клавиш. Я их набираю одним движением, не задумываясь, что это такое там делают мои пальцы. Когда я писал этот абзац, мне даже пришлось потратить некоторое время, чтобы осознать, что же именно я при этом нажимаю. В общем, полнейший автоматизм.

А вот с мышью автоматизм не вырабатывается. Нельзя вслепую ткнуть куда-то курсором — в этом процессе всегда участвует сознание. Сначала нужно увидеть некий визуальный маркер (кнопку, пункт меню), затем подвести туда курсор, используя обратную связь через глаза для точного позиционирования, затем щелкнуть кнопкой. Одним кликом часто дело не ограничивается, поэтому повторяем все снова. Так как в этом процессе участвует сознание, из кратковременной памяти вытесняются важные сведения. Эффективность падает.

Конечно, я все это говорю не про мышь и не про клавиатуру. И не про Emacs. И я не хочу ни за что агитировать и ни к чему призывать. Я просто хочу сделать вывод — следствие из утверждения из начала поста:

Для повышения эффективности следует пользоваться инструментами, допускающими бессознательное их использование.

УжасноПлохоНормальноХорошоОтлично (2 голосов, средний: 5,00 из 5)
Loading ... Loading ...

Ссылки для C-гиков

Такая вот подборка в дополнение к опубликованному ранее:

  • C @ Interview Mantra — подборка задач на C, предлагаемых на собеседованиях.
  • C Puzzles — набор сишных головоломок, неплохо прокачивает знание тонких моментов языка.
  • C Extensions — описание того, что GCC привносит в C помимо требований стандарта.
  • Coroutines in C — статья о реализации сопрограмм на C.
  • How to Use the restrict Qualifier — что такое квалификатор restrict и когда его нужно использовать.
  • Ampifying C — совершенно сносящая крышу статья, описывающая применение генерации C-кода из Lisp-кода.
УжасноПлохоНормальноХорошоОтлично (1 голосов, средний: 5,00 из 5)
Loading ... Loading ...

Экономим свое время в bash

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

У меня по этому поводу всплыла в памяти одна цитата:

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

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

Так вот bash (и я вслед за ним) исповедует как раз такую философию: затраченные однажды усилия не должны затрачиваться снова. Ниже я приведу несколько советов, которые в рамках этого подхода позволяют иногда сэкономить несколько секунд. Мелочь, но с миру по нитке…

CDPATH

Переменная окружения CDPATH является аналогом переменной PATH с той разницей, что вторая определяет пути поиска исполняемых файлов, а первая — пути поиска каталогов, задаваемых в команде cd.

Если CDPATH не установлена, то поиск происходит только в текущем каталоге.

Обычно соответствующая строка в ~/.bash_profile выглядит как-то так:

export CDPATH=.:~:~/Dropbox:~/dload:~/wspace

Теперь, например, если в каталоге ~/dload есть подкаталог bibl, то каким бы ни был текущий каталог, команда cd bibl приведет нас в ~/dload/bibl.

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

alias

Команда alias позволяет создать псевдоним — короткое имя для любой команды или их последовательности. Чтобы создать псевдоним, нужно выполнить:

alias name="command"

Где, понятное дело, name — имя псевдонима, а command — что нужно выполнить, когда мы набираем в консоли name. Например:

alias pa="ps -A | grep"

дает нам новую команду pa, с помощью которой удобно искать процессы по части их имени. Обычно все вызовы alias записываются в файле ~/.bashrc.

Команда unalias выполняет обратное действие, то есть уничтожает указанные псевдонимы. Ключ -a предписывает удалить все псевдонимы.

Если выполнить alias без параметров, она выведет список всех определенных псевдонимов, причем в виде команд, которыми они создаются.

Кстати, имя псевдонима вполне может совпадать с именем существующей команды. Более того, это часто используется для задания командам «параметров по умолчанию». Например, у меня есть такой псевдоним:

alias ls="ls --color=auto"

Теперь «команда» ls будет раскрашивать свой вывод, и это не придется указывать явно. Если же мы хотим обратиться к исходной команде (то есть, временно запретить раскрытие псевдонимов), нужно перед ее именем поставить символ «\» (\ls).

Повторение набранного

Существуют специальные комбинации символов, хранящие последнюю введенную команду и ее аргументы:

  • !! — вся команда целиком;
  • !* — все аргументы;
  • !:2 — второй аргумент;
  • !$ — последний аргумент.

Чаще всего нужно обратиться или ко всей команде, или к последнему аргументу. Например, можно создать псевдоним на основе последней команды:

alias foo="!!"

или повторно выполнить команду от имени root:

sudo !!

или что-нибудь сделать с только что созданным файлом:

touch longscriptname.sh
chmod +x !$

В общем, вариантов много, проявите фантазию.

Поиск по истории команд

Это, наверное, и так все знают, но все же. Жмем Ctrl-r, вводим часть команды, затем жмем Ctrl-r до тех пор, пока не отобразится нужная команда. Enter.

На этот раз все. Призываю читателей поделиться своим bash-кунг-фу (по желанию добавлю в пост).

———> Постовой

Теперь все знают, что crystalbit ведёт delphi блог и обсуждает там аспекты программирования.

УжасноПлохоНормальноХорошоОтлично (4 голосов, средний: 4,75 из 5)
Loading ... Loading ...