Записи с темой: программирование (список заголовков)
23:59 

Клоуны

Я: Если будешь плохо учиться, станешь garbage collector.
Коллега поднимает руки и с характерной интонацией кричит:
-- СВОБОДНАЯ ПАМЯТЬ!

@темы: Программирование, Викторика

23:59 

Приоритет ?:

Несмотря на то, что я повесил на стенку на рабочем месте таблицу с приоритетами операций в Си/Си++, всё равно иногда напарываюсь.

Я очень люблю операцию ?:. Она позволяет многие длинные конструкции заменять на более короткие -- и более понятные. Хотя наворотить с ней можно и совершенно непонятную фигню.

Вот, например, вместо:

if(a>3) a++; else a--;

можно написать:

a+=a>3?1:-1;

Не всем кажется, что второй вариант понятен, но я не про это.

Приоритет этой операции один из самых низких. Он такой же, как у присваивания, поэтому в данном случае всё правильно работает только за счёт того, что ассоциативность у присваивания -- справа налево (за счёт этого работают штуки вроде a=b=c=0;). То есть, ?: вычисляется раньше, чем присваивание.

Но могут быть и более сложные случаи, например:

a=x*invert?1:-1;

Тут ошибка. Сначала икс умножится на инверт, в то время как задумка была в том, чтобы a получила значение либо x, либо -x. Вот на этом-то я и накололся.

Поэтому ?: надо всегда заключать в скобки:

a=x*(invert?1:-1);

@темы: Фейлы, Программирование

23:59 

Полиглот

Один из коллег решил заняться программированием. Помогаем чем можем.

Программист1: У нас в проектах принято использовать венгерскую нотацию.
Программист2: Но не следует её путать с обратной польской нотацией.
"Ученик": Мне теперь ещё и географию учить?!

@темы: Викторика, Программирование

23:59 

Нуар

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

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

Обычно считается хорошим стилем -- об'являть переменную непосредственно перед использованием. Но это приводит к неожиданным факапам.

int main()
{
//инициализация контроллера
for(;; )//главный цикл
{
//пять страниц кода
int state=0;//состояние автомата
//пусть автомат будет простой
//входных сигналов нет
//на каждом шагу переходит в соседнее
//из двух состояний
if(state==0)
{
state=1;
//действия
}
else
{
state=0;
//действия
}
}
}
Долго думал, почему автомат всё время в нулевом состоянии и в первое не переходит? А вот. Пришлось переносить state в начало main().

@темы: Программирование, Говнокод

23:59 

Арабский код

Коллегам из дружественной организации как-то раз надо было в микроконтроллерной программе разворачивать числа задом наперёд.

То есть, вот у нас число 183 (0b10110111), а надо было, чтобы число стало 237 (0b11101101). Как это можно сделать?

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

Но это очень долго. Они хотели быстро! Поэтому они сделали массив, содержащий в себе все возможные варианты конечных чисел. Выбирая число по индексу, можно было получить ответ. В случае с приведённым выше примером, было так:

res=table[183];//res==237

Это был эффективный метод. Только числа были не восьмибитные, а шестнадцатибитные. Посчитаем, сколько занимала такая таблица в памяти?

65536*2=128 KiB

А размер всей прошивки был 512 KiB. То есть, четверть (!) всего места занимала эта таблица. Её можно было бы сгенерировать динамически в оперативной памяти, но оперативной памяти было ещё меньше.

Для их задачи потеря четверти прошивки на такую таблицу была не критична. Но такое решение мне казалось нерациональным. Тем не менее, лучшего я всё равно не знал. Тогда.

Потом я изучил ассемблер того контроллера. А контроллер был архитектуры ARM Cortex-M. По идее, это RISC-контроллер. Однако в его наборе команд есть много полезных штучек, делающих специфичные вещи, которые иначе занимали бы много команд. Это противоречит идеологии RISC, но удобно. Например, есть команда, считающая число ведущих нулей в числе. Или команда, расширяющая знаковое число любой указанной битности до знакового числа стандартной битности (16, 32). И даже команды работы со стеком были!

И вот среди этих команд я нашёл команду RBIT. Она делала именно то, что хотели эти ребята -- переворачивала биты задом наперёд. В один такт.

Выводы и мораль писать лень.

@темы: Говнокод, Программирование

23:59 

Больше студий для бога студий

Отлаживать одновременно две программы -- норма. Допустим, клиент и сервер.

Сегодня я дошёл до отладки сразу четырёх программ:
1. Прошивка микроконтроллера;
2. Основная программа на компьютере;
3. DLL-ка, через которую основная программа связывается с устройством на микроконтроллере;
4. Программа по обработке полученных результатов (ей занимаюсь не я, но мне надо было посмотреть, что в ней происходит, т.к. она не работала).

Ну и ещё пятый проект был открыт для справки.

@темы: Программирование

23:59 

Как говорить с младенцем о семиотике

Долго ли, коротко ли, решили мы отдать исходный текст одного проекта другой организации.

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

Я думаю -- ого. Сейчас нам пришлют гуру. Мы с ним потолкуем, я чему-нибудь новому научусь.

Приходит мужик. Я ему начинаю что-то рассказывать, а потом вдруг спрашиваю -- а квалификация у вас какая?

А он отвечает -- да я вообще не программист. И программирования совсем не знаю.

Да вы шутите! Как же я ему рассказывать буду?

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

Об'яснил три раза. Задаю контрольный вопрос. И всё. Оказывается, что он даже на этом уровне ничего не понял.

У меня появилось острое ощущение, что я принимаю экзамен у студента, который не готовился. А мужик за сорок.

Я начал над ним смеяться, а он обиделся. Он сказал, что если бы он меня что-нибудь по механике спросил, он бы тоже надо мной смеялся.

Я сказал, что конечно, но мне всё равно было смешно.

@темы: Программирование, Случай из жизни

23:59 

Альтернативный метод работы с таймерами в микроконтроллерах

Я хочу рассказать историю одного говнокода, но сначала вводная.

(кручу-верчу, обмануть хочу)

@темы: Борьба с техникой, Говнокод, Программирование

23:59 

Скучное расследование тормозов при передаче данных

23:59 

Между первой и второй перерывчик небольшой

В WinAPI есть функция SetTimer. Она получает в качестве аргумента время в миллисекундах. И через заданное число миллисекунд генерирует событие, которое можно обработать.

Уменьшаю время. Уменьшаю. А эффекта нет. Полез в документацию.

ОКАЗЫВАЕТСЯ, что у SetTimer есть минимальное время, равное 10 мс. Если задавать меньше, всё равно событие будет генерироваться каждые 10 мс. Да, в документации про это написано. Однако поведение очень неожиданное. Например у функции Sleep, которая просто приостанавливает выполнение программы на заданное время, таких ограничений нет.

Интересно, это ограничение родилось из времён, когда компьютеры были большими? Современные компьютеры и таймер в 1 мс спокойно обработают.

@темы: Говнокод, Программирование

23:59 

2-НДФЛ

Отлаживаю передачу данных из микроконтроллера в компьютер.

Данные передаются в несколько сотен приёмов чушками по 256 байт.

Я (сам себе): Так, я должен получать 600 кусков. А получаю только 500 кусков. И не понимаю, почему.
Коллега: Ты свою зарплату считаешь?

@темы: Случай из жизни, Программирование, Викторика

23:59 

Метод вытянешь -- именованная труба увязнет

Я: Делал то-то и то-то. Сделал. Работает. Но вот другая штука сломалась. Сейчас разбираюсь, почему.
Начальник: Как это так получилось, что делал одно, а сломалось другое?
Я: А у вас что, так не бывает?
Начальник: Нет, не бывает.
Я: Может быть, вы мало программированием занимаетесь?
Начальник: Двадцать лет... да, наверное, мало.

Тут подключается наш третий программист:
Третий: У меня тоже такого не бывает.
Я: Что, совсем?
Третий: Ну... бывает всё-таки. Редко.

***

Вопрос, в первую очередь, к программистам -- часто ли у вас выходит из строя одна часть программы, когда вы делаете другую? Как с этим бороться?

У меня такое случается часто, особенно при работе с проектами, у которых, так скажем, богатая история.

@темы: Программирование

23:59 

Об ошибках в POS-терминалах

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

Поскольку в основе -- ПК, а на нём крутится винда и какое-то ПО, всё это изредка падает. Выводятся соответствующие сообщения об ошибках, синие экраны и прочее.

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

Не я один дивлюсь на такое, в интернете куча фоток. Я сам тоже снимаю, если вижу. Вот, например, захожу я как-то раз в одно заведение и вижу там на кассе вот что:



Хотя испытать удивление в связи с нетипичной ситуацией -- это очень хорошо, но для владельцев экранов отвалившаяся декорация -- это плохо. Хотя все всё понимают, но демонстрация внутренностей всё равно снижает доверие к владельцам. Непорядок у них!

Как же должно вести себя ПО на компьютерах, экраны которых постоянно видят клиенты?

1. Желательно, чтобы сообщения об ошибках на такие экраны не выводились вообще. Должно быть просто написано "устройство временно не работает". Примерно так пишут банкоматы Сбербанка. Клиентам незачем знать, что там сломалось. Если устройство предназначено для пассивного вывода информации, либо работают с ним только сотрудники, но не клиенты, то можно вообще выводить логотип организации без пояснений. Тогда клиенты даже не догадаются, что что-то сломано. Очевидно, сотрудники должны уметь отличать логотип, сигнализирующий об ошибке, от обычного.
2. Естественно, работники, либо сервисный инженер, должны иметь возможность узнать, что произошло на самом деле. Это можно реализовать различными способами, к примеру, если нажать определённую комбинацию клавиш на терминале, откроется окно с текстом ошибки. Также возможна запись в лог-файл и последующий дистанционный его просмотр.
3. Тексты сообщений об ошибках должны быть на простом русском языке. Если работник в состоянии сам исправить ошибку, по тексту должно быть понятно, что делать ("отсутствует питание: вставьте вилку в розетку"). Если сообщение предназначено для сервисного инженера, оно должно легко читаться вслух, чтобы инженер мог оказать помощь по телефону. Проще всего выводить код ошибки.

Философский вопрос, следует ли перезапускать программу, если возникла ошибка? Или надо держать её в состоянии ошибки до прибытия помощи? Я считаю так: если ошибка предусмотрена разработчиками и корректно обработана И нет серьёзной необходимости, чтобы ПО постоянно работало, то пусть себе висит в состоянии ошибки. Если же ошибка не предусмотрена (типа access violation) либо если ПО должно работать 24/7, то лучше программу перезапустить. То есть:

4. Должна быть специальная сторожевая программа, которая проверяет, жива ли основная. Сторожевая программа должна уметь определять нестандартные ситуации (и особенно access violation) и при малейшем подозрении перезапускать основную программу. Основная программа, в свою очередь, должна проверять, жива ли сторожевая, и запускать её, если упала уже она.
5. Продолжение 4. Предусмотренные сообщения об ошибках должны выводиться в стиле основного интерфейса. Любые стандартные окошки винды, панель задач и т.п. -- недопустимы. Если основная программа падает всё время, сторожевая должна запускать альтернативную программу, которая будет уметь показывать на экране только полноэкранный логотип на переднем плане. Шанс, что такая программа сфейлится в работе, гораздо ниже, чем шанс фейла основной.

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

@темы: Программирование, Мысли, Восприятие

23:59 

Как работает стек в PIC32 (MIPS)?

По-видимому кроме STM32 (ARM) теперь я буду работать ещё и с PIC32 (MIPS). Стал разбираться, что к чему. И что-то оказалось, что в MIPS всё не как у людей. Про статусное слово процессора ничего не видно. Про стек ничего не видно. Что происходит вообще?

Со статусным словом я потом разобрался -- оно хранится в сопроцессоре! И в нём нету флагов результатов арифметической операции. Но ладно, команды ветвления и без этого работают, проверяя результат напрямую.

А вот со стеком... в общем, стека в MIPS действительно нет. Программист, если хочет, может реализовать его в виде программной эмуляции. Ясное дело, что стек всем нужен, поэтому компиляторы Си автоматически делают эту эмуляцию. Выглядит это примерно так:

Каждый раз, когда нужен push:

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

Каждый раз, когда нужен pop:

1. Забрать по адресу из регистра число.
2. Прибавить к регистру четыре.

ААА!

Из-за этого оверхеда один товарищ, который давно с pic32 работает, старается локальные переменные вынести по максимому в глобальные. Типа для скорости. Потому что локальные переменные хранятся на стеке. Хотя так ли велика потеря?

UPD. Потеря такова.

1. Согласно документации, пуш и поп в STM32 (ARM) занимают 1+N тактов, где N -- число сохраняемых регистров (они задаются списком). См, например, Cortex M-4 r0p0 Technical Reference Manual, Issue B, p. 3-6, Table 3-1.
2. Судя по описанию работы конвейера в PIC32, все команды кроме команд умножения, деления и FPU выполняются за один такт. Хотя мне не удалось найти, где про это написано явно.

Таким образом, оверхед при сохранении/загрузке одного регистра одинаковый (без учёта конвейерной оптимизации). Если регистров несколько, то у STM32 (ARM) небольшое преимущество, однако:
1. В зависимости от реализации конвейера в конкретном PIC32 а также получившегося кода общее время выполнения при той же частоте у PIC32 может оказаться даже меньше.
2. Далеко не вся работа со стеком заключается в использовании пуш и поп. Если локальная переменная хранится в стеке, то обращение к ней будет просто load/store командой, которая занимает 2 такта в STM32 (ARM) и, по-видимому, 1 такт в PIC32. Кроме того, работа с переменными в стеке в таком случае не будет отличаться от работы с глобальными переменными (по времени).
3. При таком большом количестве регистров общего назначения появляется возможность размещать часть локальных переменных в регистрах процессора, а не в стеке. И у PIC32 возможности тут шире, т.к. регистров больше.

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


Вот я не знаю, может быть, я что-то не понимаю в архитектурах, но почему нельзя было сделать встроенные пуш и поп? Что-что, говорите? Потому что это RISC? Так ARM тоже RISC. И там есть отличные пуш и поп.

@темы: Программирование, Крик души, Говнокод, Борьба с техникой

23:59 

О цветах в программах

Лучше всего, конечно, полагаться на системные настройки. Но иногда хочется добавить каких-нибудь красивостей в оформлении. Чтобы всё было жёлтым написано по красному, к примеру.

И вот какую ошибку я хочу предупредить. Берясь за цветовое оформление, следует забирать под своё крыло его целиком. В базовом случае -- и цвет шрифта, и цвет фона. Потому что системный цвет фона на вашем компьютере может отличаться от цвета фона у пользователей. И тогда может получиться голубой текст на сером фоне. Это еле читаемо. А у вас всё было хорошо, потому что стояла цветовая схема с чёрным фоновым цветом.

Винда предлагает кроме конкретных цветов ещё несколько констант, имеющие специальные названия (их я сходу не вспомню... что-то типа "цвет подсказок для правого нижнего угла"). Их использовать тоже не следует, т.к. эти цвета не прибиты к палитре, а тоже меняются от компьютера к компьютеру.

А всё это я встретил в программе ASUS AiSuite 3. Ей большой привет. При этом у меня стояла стандартная схема оформления. Читаемый вариант я получил только переключившись на высококонтрастную.

@темы: Программирование, Говнокод

23:52 

Без наркоза

Медианная фильтрация -- отличная штука. Спасает от случайных выбросов в данных.

В Матбале, естественно, есть готовая функция. А в Си -- нету. Мне сказали -- а возьми код из нашего соседнего проекта. Там большая, проверенная математическая библиотека есть. Мы её сами писали в своё время.

(что было дальше)

@темы: Борьба с техникой, Говнокод, Программирование

23:59 

О версионности

Приносят прибор на основе микроконтроллера. Неправильно работает.

Смотрю -- эту ошибку я уже видел. И уже исправил. Почему же прибор работает не так, как надо? Проверяю 10 раз исходник -- не может быть, чтобы ошибка была не исправлена!

Потом я догадался -- в прибор была прошита старая прошивка. Это был единственный прибор, который я ещё не прошил исправленной версией.

Так я познал очевидные истины:
1. Версии прошивки должны нумероваться (и быть приуроченными к коммитам!).
2. Устройство должно уметь сообщать о своей версии.
3. Изготовителю надо вести учёт, в каком из приборов какая прошивка стоит (если, конечно, пользователи не могут прошивать прибор самостоятельно).

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

@темы: Борьба с техникой, Лайфхак, Фейлы, Программирование, Очевидное-невероятное

23:59 

Пьер Менар

При переписывании кода с MATLAB на Си, надо помнить самое главное правило:

В Си индексация массивов всегда начинается с нуля. А в MATLAB всегда начинается с единицы!

После исправления всех ошибок индексации сходство результатов достигает 5 и более значащих цифр.

@темы: Борьба с техникой, Программирование

23:59 

Аппарат абонента не существует

Приделал к проекту очередную фишку. Было свободное время, т.к. коллега свою часть ещё не доделал. Дай, думаю, посмотрю, какие предупреждения выдаёт компилятор. Я человек простой -- предупреждения компилятора, как правило, игнорирую. Но знаю одного человека, который не начинает отладку, пока не избавится от всех предупреждений.

Так вот, он прав.

Короче говоря среди кучи declared but never used и incompatible pointer type (я слышу возглас из зала: "тебе пора переходить на Rust!") я обнаружил действительно серьёзную вещь. "returning pointer to local variable".

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

Автор кода выделял динамический массив:

int a=new int[n];

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

int a[3];

И удалил строку с освобождением памяти. А что происходит с этим массивом -- не посмотрел. В общем, он возвращался из функции:

return a;

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

* * *

Коллега по этой ситуации предложил интересную аналогию: представь себе, что ты дома положил на стол сотовый телефон и пошёл на работу. А на работе у себя на столе начинаешь его искать в том же месте, но почему-то не находишь!

@темы: Говнокод, Программирование

23:59 

Программа, электроника и механика: надёжность

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

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

С отладкой механических устройств я по работе почти не сталкивался. Но в те пару раз, когда это всё-таки происходило, я обнаружил ещё более интересную вещь: неполадки в механической части ещё сложнее диагностировать и устранить, чем в электронной. Штанги не доходят туда, куда нужно (а если доходят, то не фиксируются в нужном положении). Колёсики заедают. А "посмотреть", что происходит на самом деле -- возможности практически нет.

Однако, существует широко распространённое мнение, что если устройство функционирует чисто механически -- то это "на века". Электронные приборы гораздо нежнее и легко выходят из строя. А уж программа -- это вообще что-то эфемерное. Каждый первый разработчик ПО в лицензионном соглашении пишет, что отказывается от ответственности за то, что сотворила его программа (и за то, что не сотворила, тоже). А программы, за которые разработчик отвечает своей шкурой, стоят совсем других денег.

Откуда же берётся, что механическое устройство такое надёжное? Дело в сложности. Те механические устройства, которые надёжные, содержат в себе, к примеру, сотни элементов. Они перемещаются, к примеру, 10 раз в секунду, не больше. Или просто вертятся без остановки. А процессор? Сколько в нём транзисторов? Зависит от процессора, но, к примеру, 731 млн (2008 год, Core i7 "Bloomfield"). А частота -- 2 ГГц (он же). Это частота, с которой могут перекидываться транзисторы внутри. Если бы механическое устройство состояло из такого же количества частей, оно бы проработало весьма недолго. Ибо поломка любой детали -- кирдык.

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

С программами дела обстоят несколько иначе. Если программа не зависит от внешних факторов, кроме предусмотренных "входных данных", её надёжность будет примерно такая же, как у электроники, на которой она работает. То есть -- прошивка микроконтроллера даст сбой либо из-за ошибок в самой прошивке (которые относительно просто обнаружить), либо из-за сбоя электроники. Но на практике прошивки редко воспринимаются пользователями как "программа". Когда говорят о надёжности программы, имеют ввиду чаще всего прикладное ПО, работающее под какой-либо ОС. И на этой ОС кроме самой программы тусуется ещё десяток посторонних программ. И всё это взаимодействует друг с другом и с ОС непредсказуемым образом. То есть, выход программы из строя может быть вызван неизвестным количеством внешних факторов (помимо сбоев электроники и ошибок самой программы). Кто же согласится нести ответственность за такое?

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

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

@темы: Программирование, Мысли, Борьба с техникой, Наблюдения, Электроника

Untitled

главная