си
«Гопник 2»: порт под Линукс и МакОС
Я искал медь, а нашёл золото! Постоянные читатели блога знают, что я маниакально одержим текстовой игрой «Гопник» и её продолжением, которые были написаны ещё под ДОС. Хотя её время давно прошло, я постоянно пытаюсь разыскать
Можно даже сказать, что я председатель
В общем иногда на меня накатывает и я опять начинаю шерстить интернет в поисках
И вот наконец мне крупно повезло!
Как оказалось, автор второй части
Интернет со временем всё забывает и чтобы это не случилось и с этим портом, я выложил его на «Гитхаб», минимально причесал, внёс исправление для компиляции под МакОС и пофиксил один баг (при переходе в метро игра падала с ошибкой).
В планах починить сохранение и загрузку состояния игры — Андрей
«Крестики-нолики» и printf
Очень люблю так называемое ненормальное программирование (нормального мне и на работе хватает), когда программируют ради преодоления
На днях один из читателей очень меня порадовал игрой «
Для тех, кто не знает, поясню, язык printf — это значки, которыми описывается в каком именно виде нам хочется вывести на экран информацию о переменных. Используется во многих языках, но особенно часто в Си.
В этом языке много интересных конструкций, есть даже такие, которыми можно даже
Например, вот небольшой пример, который запишет число «42» (длину строки «Answer to the Ultimate Question of Life is») в переменную c:
#include <stdio.h>
int main() {
int c;
printf("Answer to the Ultimate Question of Life is%n ", &c);
printf("%d", c);
}Как, используя эти значки, можно
Если закодировать «ложь» пустой строкой, а «истину» строкой длиной один, то логическую операцию «ИЛИ» можно получить, если померить совокупную длину двух входных значений:
// c = strlen("") + strlen("")
printf("%s%s%n", "", "", &c); // в c будет «0»
// c = strlen("1") + strlen("")
printf("%s%s%n", "1", "", &c); // в c будет «1»«НЕ» получается сложнее, для этого надо использовать несколько хитростей.
Всё это позволяет закодировать «НЕ» как (strlen(a)+255)%256 или strlen(a)-1:
printf("%1$255s%1$s%2$hhn", a, &c);Тут написано следующее: вывести первый параметр как строку, дополнив его до длины в 255 символов пробелами (%1$255s), снова вывести первый параметр как строку (%1$s), записать полученную длину по модулю 256 во второй параметр (%2$hhn).
Отсюда легко понять как сделать операцию
printf("%1$254s%1$s%2$s%3$hhn", a, b, &c);В принципе, этого хватило, что реализовать всю требуемую логику.
Играют два игрока, последовательно вводя номер ячейки, куда делается ход (от единицы до девяти, считая слева направо, сверху вниз). Программа отрисовывает поле и определяет победу одного из игроков или ничью.
Очень заморочено и очень интересная идея!
Алгол и Баш
Удивительно, я и не догадывался, что иногда употребляю ключевые слова родом
Оказывается автор командной оболочки bsh Стивен Борн притащил ключевые слова IF/FI и CASE/ESAC прямиком из «Алгола». Позже оттуда они попали в более известный сейчас bash, на котором я нередко пишу.
Более того, Стивен Борн по всей видимости насколько крепко фанател по этому языку, что специальными макросами превратил Си, на котором написал bsh, в некое подобие «Алгола» (исходники есть в интернете).
Если скачать себе пару заголовочных файлов (и выкинуть из них всё то, что мешает компиляции), то можно и сейчас попробовать этот макроязык в деле (это я пузырьковую сортировку написал):
#pragma GCC diagnostic ignored "-Wimplicit-int"
#pragma GCC diagnostic ignored "-Wreturn-type"
#include "mac.h"
#include "mode.h"
#include <stdio.h>
bubbleSort(arr, n)
INT arr[], n;
BEGIN
INT i, j;
FOR i = 0; i < n - 1; i++
DO FOR j = 0; j < n - i - 1; j++
DO IF arr[j] > arr[j + 1]
THEN arr[j] = arr[j] - arr[j+1];
arr[j+1] = arr[j] + arr[j+1];
arr[j] = arr[j+1] - arr[j];
FI
OD
OD
END
printArray(arr, n)
INT arr[], n;
BEGIN
INT i;
FOR i = 0; i < n; i++
DO
printf("%d ", arr[i]);
OD
printf("\n");
END
main()
BEGIN
INT arr[] = { 0xFE, 0xED, 0xBE, 0xEF };
INT n = 1[&arr] - arr;
printArray(arr, n);
bubbleSort(arr, n);
printArray(arr, n);
ENDПримерно так выглядят исходники bsh. Единственное моё добавление — BEGIN и END, которые Борн для описания тела функций не использует, но зато в «Алголе» принято делать именно так.
Кстати (век живи — век учись), такое неординарное объявление функций, как у меня в листинге (а я подсмотрел его в исходниках bsh), — как оказалось, устаревший синтаксис Си, причём он до сих пор поддерживается компиляторами.
День Радио ★
Сегодня, 7 мая в России отмечают День радио — в этот день, 125 лет назад российский физик Александр Степанович Попов на заседании Русского
К этому юбилею, превозмогая лень, прокрастинацию и уныние от «самоизоляции», мне удалось наконец дописать игрушку, которую я задумал ещё несколько лет назад. Игра была готова неделю назад, но я всё никак не мог сделать последний шаг — добавить описание и выложить наружу.
Для практики в моей любимой дисциплине — «ненормальном программировании», очень хотелось написать
В итоге, для этого я выбрал «Морской бой». В моём варианте игрок вводит информацию клавишей левого «Шифта», а компьютер отвечает, мигая светодиодом кнопки «Капс Лок». Чуть более подробные правила есть в репозитории проекта.
Обёртка и некоторые функции (мигание светодиодом, опрос «Шифта» и функция задержки) написаны на Си, остальная часть — на языке «Лу́а» (Lua). Давно хотелось изучить этот странноватый язык поглубже, а тут ещё удалось поучиться встраивать его в Си.
Чтобы увеличить градус безумия всего проекта, в Си были использованы диграфы — они очень странно выглядят и дают ощущение, что используется
Пока работал над игрой, приходилось много гуглить, в очередной раз поразился, как много кода люди бездумно копируют друг у друга. Реализация мигания светодиодом, например, кочует из проекта в проект в неизменном виде — видно, что никто из скопировавших не понимает как этот код вообще работает. Я в нём попытался разобраться и написать чуть чище, чем буквально везде.
Для тех безумцев, которые захотят попробовать скопмилировать этот вариант «Морского боя» и сыграть, ещё раз привожу репозиторий проекта (только под «МакОСь») — https://github.com/bolknote/battleship.
Комментарии в Си
Из документации к языку Си на сайте «Микрософта», секция про комментарии, описывается нестандартное расширение спецификации:
Comments beginning with two forward slashes (//) are terminated by the next newline character that is not preceded by an escape character. […] the newline character is preceded by a backslash (\), creating an «escape sequence». This escape sequence causes the compiler to treat the next line as part of the previous line. (For more information, see Escape Sequences.)
The default for Microsoft C is that the Microsoft extensions are enabled. Use /Za to disable these extensions.
То есть в примере ниже, если его скомпилировать в «Визуал Студио», выражение i++; на самом деле будет частью комментарий и не выполнится:
// мой комментарий \
i++;Счастливой отладки!
Добавлено позднее: в компиляторе gcc тоже работает, причём раскрашивается как комментарий в редакторах vim и «Саблайм».
InlineC
Продолжая тему интерфейсов к языку Си, кстати будет упомянуть появившийся недавно модуль inlinec для Пайтона. Он позволяет вставлять в программу на Пайтоне
# coding: inlinec
from inlinec import inlinec
@inlinec
def Q_rsqrt(number):
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y;
i = 0x5f3759df - ( i >> 1 );
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) );
return y;
}
print(Q_rsqrt(1.234))Я проверил, это действительно работает. Тут вычисляется так называемый быстрый обратный квадратный корень от числа 1,234.
Пока этот модуль скорее прототип — скорость оставляет желать лучшего, в рабочем каталоге создаётся куча мусорных временных файлов, любое отступление от эталонного примера грозит выводом ошибки, которая совсем не помогает понять что не так.
Куда интереснее разобраться как это всё работает. Декоратор @inlinec тут ничего не делает. Судя по коду модуля, он возвращает функцию как есть и служит маркером, что ниже пойдёт сишная функция. Всё работу выполняет кодек, который подключается первой строкой.
Приём не новый, несколько лет назад я про него уже рассказывал — в Пайтоне в специальном магическом комментарии можно указать кодек, который пропускает через себя текст программы. Обычно он используется для программирования в кодировках, отличных
Итак, код перед выполнением скармливается специальному кодеку inlinec, который разбирает программу, находит в ней
Программисты-«волшебники»
Про
Хочу высказаться также по триаде «объект — указатель — ссылка».
Изначально в Си (не «++») ссылок не было. Были только объекты и указатели. Любой начинающий сталкивался с указателями, как только ему требовалось впервые изменить в теле функции значение переданного функции параметра:
void func1(int i) { i = 1; }
void func2(int* i) { *i = 1; }Далее:
int i = 0;
func1(i); // всё ещё i==0
func2(i); // вот теперь i==1Этот код демонстрирует то свойство Си,
Спрашивается, так зачем ещё потребовались ссылки? Ответ таков. В мире существует, не исчезает и даже порой ширится класс
Пусть лучше в теле func2 тоже будет написано
void func1(int i) { i = 1; }
void func1(int &i){ i = 1; }
int i = 0;
func1(i); // волшебного значка нет, i не изменилось
func2(i); // волшебный значок! i изменилось.Смотрите, операторы в теле функций одинаковые, а работает
Программисты Си долго сопротивлялись,
Числа от «n» до нуля без сравнений
На «Хабре» интересную задачу увидел, требуется написать на Си программу, выводящую числа от «n» до нуля, не используя (скрыто или явно) операторы сравнения.
Мой вариант:
#include <stdlib.h>
#include <stdio.h>
void print(const int n) {
printf("%d\n", n - 1);
}
int main(int argc, char* argv[])
{
const int N = atoi(argv[argc - 1]);
void (*f[])(int) = {print, exit};
for (int n = -1;;n++) {
f[~n/~N](N - n);
}
}Структуры в Си и CGO
По горячим следам хочу написать какая забытая мною особенность языка Си вчера ввела меня в заблуждение и почему несмотря на то, что я работал с сишной структурой, CGO так как будто не считало.
Для начала посмотрим как определяются и передаются структуры в Си. Полную спецификацию я приводить не буду, посмотрим на два распространённых способа. Первый используется в этом небольшом примере:
#include <stdio.h>
#include <math.h>
struct point {
double x, y;
};
double point(struct point p) {
return sqrt(pow(p.x, 2) + pow(p.y, 2));
}
int main() {
printf("%g\n", point((struct point){1, 2}));
}Тут можно обратить внимание на две особенности —
Это происходит потому, что имена структур обитают в своём собственном пространстве имён, на его использование и указывает struct. Там же живут имена объединений (union) и перечислений (enum). Имена функций находятся в общем пространстве, так что два таких определения с одним именем друг другу не мешают.
Структуры, объявленные таким образом видны через CGO компилятора Гоу как C.struct_name (C.struct_point в данном случае). К сожалению, тут есть особенность, о которую я споткнулся — если запрашиваемая структура не определена, то Гоу не выдаст ошибки, мы просто получим пустую структуру:
package main
import ("C"; . "fmt")
func main() {
Println(C.struct_undefined{}) // ошибки не будет
}Но программисты — люди ленивые, писать всюду struct неудобно. К счастью есть способ этого не делать — можно завести структуру, а потом дать ей имя в пространстве всех остальных типов. Это делается при помощи ключевого слова typedef позволяющего создавать алиасы типов:
struct point {
double x, y;
};
typedef struct point point;Теперь у нас есть два имени для одного типа — один в пространстве структур и прочего, второе — в общем пространстве. Можно даже сделать их одинаковыми, что очень удобно. Если второе имя нам ни к чему (бывают случаи, когда оно необходимо, но я не буду их тут рассматривать), то можно определить анонимную структуру и сразу дать имя этому типу в общем пространстве, так тоже будет работать.
Это нас подводит ко второму способу работы со структурами:
#include <stdio.h>
#include <math.h>
typedef struct {
double x, y;
} point;
double veclen(point p) {
return sqrt(pow(p.x, 2) + pow(p.y, 2));
}
int main() {
printf("%g\n", veclen((point){1, 2}));
}Ключевое слово struct перед типом уже не нужно, раз все имена в общем пространстве. Это же касается и CGO.
Как раз на эти грабли я и наступил — позабыл про эти все особенности. Когда я ещё писал на Си, всегда пользовался вторым способом, с именем в общем пространстве и привык к тому, что это и есть структуры.
Триграфы в Си
Век живи, век учись, больше заработаешь. Язык C я недолюбливаю, преимущественно
Вообще триграфы были придуманы для терминалов, в которых некоторых символов не хватает. В итоге вместо #define можно написать ??=define. Триграфы подменяются на нужные символы в самом начале, поэтому эти записи эквивалентны. Вместо { можно написать ??< , вместо } использовать ??>
Остальные триграфы можно найти в статье Елены Сагалаевой «Триграфы и альтернативные символы» на её персональном блоге. Кстати, очень рекомендую сам блог — отличное чтиво на темы связанные с программированием.
Мой mod_autoindex
Испокон веков в Apache (внимание! речь идёт об Apache 1.x) существует модуль mod_autoindex — в комментариях в его исходном коде есть дата, видимо написания первой версии — 23 марта 1993 года. С тех пор модуль, очевидно, претерпевал
С 1993 года HTML обогатился таблицами и каскадными стилями (CSS). Вряд ли сейчас
В моём портфолио есть несколько проектов, где приходилось изобретать
Стандартный модуль Apache позволяет менять, в этом плане:
- header, т. е. часть над списком файлов (HeaderName, SuppressHTMLPreamble)
- footer, часть под списком файлов (ReadmeName)
- размер иконок (IconWidth, IconHeight)
- внешний вид иконок (AddIcon, AddIconByType, AddIconByEncoding)
- убирать из списка некоторые столбцы (SuppressLastModified, SuppressSize, SuppressDescription)
- изменять описание файла (AddDescription)
- и весьма ограниченно менять внешний вид выводимой информации (FoldersFirst, SuppressColumnSorting и т. п.)
Чаще всего этого для «упаковки» списка файлов в дизайн очень мало. Для облегчения задачи интеграции вывода mod_autoindex в дизайн я несколько усовершенствовал этот модуль. Всё по порядку.
Что умеет новый модуль

- Режим FancyIndex всегда включен и не выключается. Вряд ли найдётся много людей
не использующих этот режим. - В основном режиме (об этом подробнее — чуть ниже) таблица файлов выводится при
помощи тега TABLE, где у большинства объектов (например, у первой строка, чётных
и нечётных строк) прописаны идентификаторыCSS-классов, из чего следует, что внешний
вид таблицы вы легко сможете задать при помощи файла стилей. - Новая опция, позволяющая включить в листинг файла ссылку на корень сайта
(IndexOptions +RootFolder, при использовании не забудьте добавить иконку,
например так: «AddIcon /icons/back.gif .») - Появилась возможность перенести Description в отдельную (новую) строку
(IndexOptions +DescriptionAtNewRow) - Появилась возможность локализовать (перевести на родной язык) текстовую
информацию модуля (директивы ParentStr, RootStr, NameStr, SizeStr, DescriptionStr, LastModifiedStr). - С первой страницы (если пользователь находится в корне сайта) убрана
ссылка «Parent Directory», ведущая в этом случае, на ту же самую страницу. - Появился новый режим вывода, основанный на теге SPAN (именно он на иллюстрации),
в частности, количество иконок в строке в этом режиме зависит от ширины окна браузера
(IndexOptions +BoxWidth=N +BoxHeight=N +MaxIconHeight=N)
Новый режим вывода

Новый режим вывода включается автоматически, если были использованы параметры BoxWidth и BoxHeight. Эти два параметра отвечают за размер прямоугольника, в который заключена иконка (на иллюстрации выделено чёрным) и, по сути, определяют расстояние между соседними иконками.
Размер этот нужно выбирать исходя из размеров иконки, текста названия файла и строки, где выводится размер файла, плюс некоторое пространство, необходимое для обеспечения зазора между соседними иконками. Есть ещё один параметр — MaxIconHeight, выбрать его просто — это всего лишь максимальная высота иконки в вашем наборе. Нужен этот параметр для того, чтобы подписи к иконкам располагались на одном уровне (на иллюстации — синяя линия).
В этом режиме под иконкой выводится имя файла, размер и дата модификации (если, конечно, не были использованы опции SuppressLastModified и SuppressSize), описание файла выводится в аттрибут «title» тега SPAN, охватывающего иконку и надписи (если не была использована опция SuppressDescription) — вы можете использовать DHTML, чтобы выводить информацию из title в статусную строку, всплывающую подсказку и т. п.
Пример
Файлы для вывода информации так, как это изображено на иллюстрации выглядят так:
Файл «.htaccess»:
IndexOptions +SuppressHTMLPreamble +NameWidth=*
IndexOptions +SuppressLastModified +FoldersFirst +RootFolder +DescriptionWidth=*
IndexOptions +BoxWidth=80 +BoxHeight=80 +MaxIconHeight=33
HeaderName /_bolk_autoindex/.HEADER.html
ReadmeName /_bolk_autoindex/.FOOTER.html
ParentStr "Наверх"
RootStr "В корень"
NameStr "Имя"
SizeStr "Размер"
DescriptionStr "Описание"Файл «.HEADER.html»:
<head>
<title></title>
<style type="text/css">
td,
body { background-color: white; font-family: Verdana,Tahoma; font-size:10px }
a { color: black; text-decoration:none }
.cell0, .cell1 {text-align: center}
a:hover { text-decoration: underline }
</style>
</head>
<body>
<h4>Пример использования</h4>
Содержимое каталога:
<!--#echo var="DOCUMENT_URI" encoding="none" -->
<br clear="all" /><br />Файл «.FOOTER.html»:
</body>Где взять и как установить
Файл можно взять у меня на сайте (ZIP, около 15Кб), для установки нужно переписать его на место старого модуля (каталог src/modules/standard) и перекомпилировать Apache. Проблемы и их решения
Часто, помимо имени файла, его размера и даты создания, хочется вывести другую полезную информацию — например, разрешение для картинок, ID3 теги для MP3 файлов и так далее. Для этой цели я рекомендую воспользоваться полем «Description» и директивой «AddDesription». Задачей модуля не является и, надеюсь, не будет являться (иначе это приведёт к снижению производительности) автоматический вывод информации такого рода. Напишите скрипт (кстати, возможно позднее я его выложу), который пробежит по каталогам и добавит эту информацию в соответствующие .htaccess файлы и вызывайте его далее по мере необходимости.
Один из заказчиков попросил предоставить пользователю возможность самому выбирать какой вид листинга файлов он хочет получить — табличный или в виде иконок. К сожалению, у Apache 1.x нет методов переключения конфигурации в зависимости
<VirtualHost example.com:80>
...
AccessFileName .htaccess .htaccess-table
...
</VirtualHost>
<VirtualHost example.com:443>
...
AccessFileName .htaccess .htaccess-icons
...
</VirtualHost>где прописаны одно общее имя
По всем вопросам, с пожеланиями по улучшению — добро пожаловать в мой почтовый ящик, он есть на моём сайте внизу каждой страницы.