{
    "version": "https:\/\/jsonfeed.org\/version\/1.1",
    "title": "Блоги: заметки с тегом си",
    "_rss_description": "Автоматически собираемая лента заметок, написанных в блогах на Эгее",
    "_rss_language": "ru",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": false,
    "_itunes_explicit": "no",
    "home_page_url": "https:\/\/blogengine.me\/blogs\/tags\/si\/",
    "feed_url": "https:\/\/blogengine.me\/blogs\/tags\/si\/json\/",
    "icon": false,
    "authors": [
        {
            "name": "Илья Бирман",
            "url": "https:\/\/blogengine.me\/blogs\/",
            "avatar": false
        }
    ],
    "items": [
        {
            "id": "137604",
            "url": "https:\/\/bolknote.ru\/all\/utilita-cdecl\/",
            "title": "Утилита cdecl",
            "content_html": "<p>Какая интересная утилита есть, оказывается, а я и не знал! Позволяет по любой декларации типа в языке Си получить описание на английском или наоборот — свернуть такое описание в тип. Вот примеры:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">cdecl&gt; explain char *(* const volatile (*(*hydra)(int (*)(void), char * const * restrict))[13]) (unsigned long, char * const [static 3]);\ndeclare hydra as pointer to function (pointer to function (void) returning integer, restricted pointer to constant pointer to character) returning pointer to array 13 of constant volatile pointer to function (unsigned long integer, non-empty array 3 of constant pointer to character) returning pointer to character\n\ncdecl&gt; declare fp as pointer to function (pointer to char) returning pointer to array of pointer to char\nchar* (*(*fp)(char*))[];<\/code><\/pre>",
            "date_published": "2025-09-30T11:27:05+05:00",
            "date_modified": "2025-09-30T11:26:31+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Tue, 30 Sep 2025 11:27:05 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "137604",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "135726",
            "url": "https:\/\/bolknote.ru\/all\/chto-proishodit-kogda-main-peremennaya\/",
            "title": "Что происходит, когда main — переменная",
            "content_html": "<p>Хочу провести работу над ошибками.<\/p>\n<p>Вчера я <a href=\"https:\/\/bolknote.ru\/all\/malenkaya-sboynaya-programma-na-si\/\">писал<\/a> о сбойной программе на Си с самым коротким текстом и, как оказалось, неправильно описал почему, собственно, она вызывает сбой.<\/p>\n<p>Напомню, речь идёт о программе, которая состоит из одного слова — <tt>main;<\/tt> или <tt>int main;<\/tt>, если забыть о стандарте Си89. Я думал дело в том, что попытка запустить программу приходит к чтению переменной и передаче управления на адрес ноль, но один читателей попенял, что в <a href=\"https:\/\/web.archive.org\/web\/20130910071644\/https:\/\/llbit.se\/?p=1744\">оригинальной статье<\/a> указана другая причина.<\/p>\n<p>Я решил глянуть получившийся ассемблер, чтобы разобраться где же правда. Для этого удобно использовать утилиту <tt>objdump<\/tt>:<\/p>\n<pre class=\"e2-text-code\"><code class=\"bash\">objdump -D -d -M intel .\/small<\/code><\/pre><p>Оказалось, что я был неправ.<\/p>\n<p>Чтобы это увидеть, надо посмотреть в ассемблерном листинге код, который вызывает функцию <tt>__libc_start_main()<\/tt> — она передаёт управление функции <tt>main()<\/tt>, указатель на которую указывается в регистре <tt>rdi<\/tt>. Как мы видим, адрес, записываемый в этот регистр указывает на секцию <tt>.data<\/tt>, код в которой запрещён к запуску, именно поэтому программа и падает.<\/p>\n<p>Почему именно туда? Там хранятся все переменные, в том числе переменная <tt>main<\/tt>, то есть используется не адрес, записанный в этой переменной, как я думал раньше, а выполнение передаётся прямо на адрес, где хранится сама переменная.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2025.05.02@2x.webp\" width=\"1000\" height=\"524\" alt=\"\" \/>\n<\/div>\n<p>Кстати, это означает, что на оборудовании, где защиты памяти нет или при запуске в реальном режиме (например, из-под <i>DOS<\/i>), программа может и не упасть — просто начнёт выполнять какой-то мусор, в котором вполне могут попасться инструкции корректного завершения.<\/p>\n",
            "date_published": "2025-05-02T16:24:52+05:00",
            "date_modified": "2025-05-02T17:00:11+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Fri, 02 May 2025 16:24:52 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "135726",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "135716",
            "url": "https:\/\/bolknote.ru\/all\/malenkaya-sboynaya-programma-na-si\/",
            "title": "Маленькая сбойная программа на Си",
            "content_html": "<p>Попалась мне тут <a href=\"https:\/\/web.archive.org\/web\/20130910071644\/https:\/\/llbit.se\/?p=1744\">статья<\/a> про задачку, которую автору задали в университете — написать самую маленькую (по объёму текста) программу на Си, которая компилируется, но выполняется с ошибкой. Прежде чем читать, я попробовал написать такую самостоятельно.<\/p>\n<p>Сначала я подумал о рекурсии:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">main(){main();}<\/code><\/pre><p>Для экономии байт можно использовать стандарт Си89, где всё, у чего нет при определении задания типа, имеет тип <tt>int<\/tt> — это позволяет убрать тип функции <tt>main<\/tt>.<\/p>\n<p>Программа компилируется, но, как и задумывалось, падает с ошибкой:<\/p>\n<pre class=\"e2-text-code\"><code class=\"plaintext\">bolk@note ~$ gcc-14 -std=c89 small.c &amp;&amp; .\/a.out\nSegmentation fault: 11<\/code><\/pre><p>Дальше я подумал, что вряд ли в статье используется такой очевидный способ и, перебрав в уме несколько вариантов, решил сосредоточится на делении на ноль. Лучший вариант, который мне пришёл в голову, вот такой, он на один байт меньше:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">main(a){a\/=0;}<\/code><\/pre><p>Очевидная идея — поделить число на ноль и никуда не присваивать, работать не будет, компилятор просто выкинет константное выражение. Поэтому его надо или присвоить переменной или вернуть. <tt>return<\/tt> занимает больше присваивания, но куда бы мы могли присвоить? Нужна же переменная.<\/p>\n<p>Переменную можно взять из аргументов функции <tt>main<\/tt>, в первый её аргумент записывается число параметров при вызове. Если ноль поделить на ноль, это ошибки не вызовет, но там всегда не ноль, потому что нулевой параметр всегда существует и содержит имя программы.<\/p>\n<p>Поэтому мы можем поделить его на ноль и программа упадёт при вызове:<\/p>\n<pre class=\"e2-text-code\"><code class=\"plaintext\">bolk@note ~$ gcc-14 -std=c89 -Wno-div-by-zero small.c &amp;&amp; .\/a.out\nFloating point exception: 8<\/code><\/pre><p>Но самая короткая сбойная программа оказалась почти втрое меньше. Выглядит она так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">main;<\/code><\/pre><p>Она компилируется, хоть и с замечанием, и падает, как и требуется по условию:<\/p>\n<pre class=\"e2-text-code\"><code class=\"plaintext\">bolk@note ~$ gcc-14 -std=c89 small.c &amp;&amp; .\/a.out\nsmall.c:1:1: warning: data definition has no type or storage class\n    1 | main;\n      | ^~~~\nBus error: 10<\/code><\/pre><p>Тут определяется статическая переменная <tt>main<\/tt> с неявным заданием типа <tt>int<\/tt>, которая, как все такие переменные, инициализируется нулём. Поскольку компоновщик видит имена, но ничего не знает о типах, он связывает адрес переменной с адресом точки входа в программу. <s>Когда программа запускается, управление передаётся по адресу ноль, что вызывает ошибку.<\/p>\n<p>Ошибка вызывается, кстати, потому что адрес ноль является зарезервированным значением в Си. Ноль обозначает, что указатель пустой (имеет значение <tt>NULL<\/tt>), поэтому при обращении к нему срабатывает специальная ловушка, вызывающая ошибку. Правда это возможно только на платформах с защитой памяти.<\/s><\/p>\n<p><i>Добавлено:<\/i> <a href=\"https:\/\/bolknote.ru\/all\/chto-proishodit-kogda-main-peremennaya\/\">Оказалось<\/a>, что я неправильно разобрался в происходящем.<\/p>\n",
            "date_published": "2025-05-01T14:00:46+05:00",
            "date_modified": "2025-05-02T16:25:15+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Thu, 01 May 2025 14:00:46 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "135716",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "135321",
            "url": "https:\/\/bolknote.ru\/all\/popravil-oshibku-v-bc\/",
            "title": "Поправил ошибку в bc",
            "content_html": "<p>Вчера часа проснулся около часа ночи, сна ни в одном глазу. Ворочался-ворочался, думаю надо бы заняться чем-нибудь, мозг утомить. Решил почитать исходный код <tt>bc<\/tt>. Он остался у меня с того раза, когда я <a href=\"https:\/\/bolknote.ru\/all\/uskorenie-operaciy-v-bc\/\">коммитил туда<\/a> своё предложение по ускорению функции <tt>band()<\/tt>.<\/p>\n<p>Бродил по коду туда-сюда, читал случайные куски, пока не дошёл до кусочка, проверяющего дублирующиеся параметры у функции. Вот это место:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">for (i = 0; i &lt; f-&gt;autos.len; ++i)\n{\n\t\/\/ Get the auto.\n\tBcAuto* aptr = bc_vec_item(&amp;f-&gt;autos, i);\n\n\t\/\/ If they match, barf.\n\tif (BC_ERR(idx == aptr-&gt;idx &amp;&amp; type == aptr-&gt;type))\n\t{\n\t\tconst char* array = type == BC_TYPE_ARRAY ? &quot;[]&quot; : &quot;&quot;;\n\n\t\tbc_error(BC_ERR_PARSE_DUP_LOCAL, line, name, array);\n\t}\n}<\/code><\/pre><p>Посмотрел что тут происходит и заметил, что в перечислении, описывающем виды параметров, вообще-то три значения — скаляр, массив и массив, передающийся по ссылке. При этом код выше написан так, как будто их только два. Небольшой тест показал, что так и есть. Следующий код должен порождать ошибку, между тем, он выполняется как ни в чём не бывало:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">define a(*t[], t[]) {}<\/code><\/pre><p>Подумал, что уже достаточно утомился, чтобы лечь спать, но не тут-то было! Стоило закрыть глаза, как мозг начал продумывать как поизящнее исправить этот баг. Пришлось снова открыть ноутбук и <a href=\"https:\/\/github.com\/gavinhoward\/bc\/pull\/89\">править<\/a>.<\/p>\n<p>Уже утром я прочитал сообщение по моему коммиту — автор <tt>bc<\/tt> попросил отформатировать код и написать небольшой тест. Я всё исправил и теперь ещё немного моего кода есть в этой прекрасной утилите!<\/p>\n",
            "date_published": "2025-03-25T10:55:32+05:00",
            "date_modified": "2025-03-25T11:23:59+05:00",
            "tags": [
                "bc",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Tue, 25 Mar 2025 10:55:32 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "135321",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "134844",
            "url": "https:\/\/bolknote.ru\/all\/throttling-game\/",
            "title": "Throttling game",
            "content_html": "<p>В полку́ странных игр прибыло! Помните я <a href=\"https:\/\/bolknote.ru\/all\/powershoot\/\">писал игру<\/a>, где надо управлять человечком, подключая и отключая зарядку «Макбука»? Вчера вечером написал небольшую игру из той же оперы.<\/p>\n<p>Фабула игры такова: у одного из блоков атомной электростанции сработала система предупреждения — все три уровня защиты вырубились, теперь управлять реактором нужно вручную. График загрузки процессора символизирует температуру внутри блока — много плохо, мало — тоже плохо.<\/p>\n<p>Чтобы поиграть в эту игру, надо открыть рядом «Монитор ресурсов» и вывести график загрузки процессоров. Частоту обновления («Вид» → «Частота обновления») надо поставить «Очень часто».<\/p>\n<p>После этого можно запустить игру и, управляя загрузкой процессора через клавишу «Касплок» («капс» включен — процессор загружен), держать загрузку между 10 и 90%. После старта у вас будет четыре секунды, чтобы подготовиться, вывести все нужные окна на передний план и нащупать кнопку.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2025.02.25@2x.webp\" width=\"1000\" height=\"265\" alt=\"\" \/>\n<\/div>\n<p>На идею меня навела бессмертная игра «Посадка на Луну» для калькулятора «Электроника МК-61» и код, который я делал когда-то для одного из старых проектов — у меня была идея выводить пульсацию проигрываемой мелодии в «Мониторинг ресурсов», рисуя её в окошках загрузки ядер процессора. Тогда у меня был «Макбук» с четырьмя ядрами и для каждого «Мониторинг» выводил своё окошко.<\/p>\n<p>Из-за инерции, с которой рисуется загрузка процессоров, сделать это не удалось, а код лежал в архиве до вчерашнего вечера.<\/p>\n<p>Код игрушки лежит в <a href=\"https:\/\/github.com\/bolknote\/throttling-game\">моём репозитории<\/a>.<\/p>\n",
            "date_published": "2025-02-25T10:58:34+05:00",
            "date_modified": "2025-02-25T10:58:30+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Tue, 25 Feb 2025 10:58:34 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "134844",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "133424",
            "url": "https:\/\/bolknote.ru\/all\/nechyotnye-chisla-uint32-t\/",
            "title": "Нечётные числа: uint32_t",
            "content_html": "<p>В общем-то, мне надоело ждать и <a href=\"https:\/\/bolknote.ru\/all\/nechyotnye-chisla-progress\/\">греть воздух<\/a> своим ноутбуком, поэтому я остановил программу и решил резко сократить множество перебираемых чисел, а именно — перейти на 32 бита.<\/p>\n<p>В программе есть место  жёстко завязанное на тип, — печать числа, а мне хотелось сделать так, чтобы при смене типа ничего не ломалось. Поэтому я решить попробовать обобщённое программирование. Его в Си мне использовать ещё не приходилось, вот и выпал случай.<\/p>\n<p>Заодно попробовал в действии функции <tt>__builtin_*_overflow<\/tt>, позволяющие использовать математику с контролем переполнения. Я только недавно задумался о том, что они должны бы быть в языке, а до этого переполнение я проверял дедовским способом — смотрел не стал ли результат меньше аргумента.<\/p>\n<p>Обобщённое программирование выглядит следующим образом:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">#define PRINT_U(x) ({ \\\n    _Generic((x), \\\n        uint128_t: printf_u128, \\\n        uint64_t: printf_u64, \\\n        uint32_t: printf_u32 \\\n    ); \\\n})(x)<\/code><\/pre><p>Тут у меня макрос <tt>PRINT_U<\/tt> зависит от типа аргумента, а выбор делается при помощи стандартной конструкции <tt>_Generic<\/tt> — она принимает аргумент и, в зависимости от того выражение какого типа было передано, возвращает одно значение из указанного списка.<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">static void printf_u128(const uint128_t v) {\n    unsigned long long low, high;\n\n    #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__\n        low  = MASK64(v);\n        high = MASK64(v &gt;&gt; 64);\n    #else\n        high = MASK64(v);\n        low  = MASK64(v &gt;&gt; 64);\n    #endif\n\n    printf(&quot;0x%016llX%016llX\\n&quot;, high, low);\n    fflush(stdout);\n}\n\nstatic inline void printf_u64(const unsigned long long v) {\n    printf(&quot;0x%016llX\\n&quot;, v);\n    fflush(stdout);\n}\n\nstatic inline void printf_u32(const unsigned int v) {\n    printf(&quot;0x%08X\\n&quot;, v);\n    fflush(stdout);\n}<\/code><\/pre><p>Пока отлаживал, нашёл пару досадных багов в исходной программе. Получается, зря мой ноутбук трудился, результат всё равно можно было бы выкинуть.<\/p>\n<p>Функции переполнения у меня используются вот так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">#define MUL2_ADD1(x) ({ \\\n    typeof(x) y; \\\n    __builtin_mul_overflow(x, 2, &amp;y) || __builtin_add_overflow(y, 1, &amp;y) ? 0 : y; \\\n})<\/code><\/pre><p>Были сомнения будут ли они работать с 128-битным типом, как я уже писал <a href=\"https:\/\/bolknote.ru\/all\/128-bitny-tip-v-si\/\">он какой-то неполноценный<\/a>, но, к моему удивлению, всё работает прекрасно.<\/p>\n<p>Любопытно, что мой ноутбук на процессоре <i>M3 Max<\/i> справился с перебором за 57 минут, тогда как компьютер с <i>Intel Core i9-9820X <\/i> на частоте 3,3 ГГц — за 168 минут. Это на одном ядре, разумеется. У меня в планах сделать многопоточность, но пока недосуг было.<\/p>\n<p>В результате нашлось 14434686 значений, из них нечётных подряд — 172 значения, потом появляются «дырки».<\/p>\n",
            "date_published": "2024-12-23T23:39:05+05:00",
            "date_modified": "2024-12-26T11:40:01+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Mon, 23 Dec 2024 23:39:05 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "133424",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "133399",
            "url": "https:\/\/bolknote.ru\/all\/nechyotnye-chisla\/",
            "title": "Нечётные числа",
            "content_html": "<p>Что же я там считаю такое, что мне понадобились <a href=\"https:\/\/bolknote.ru\/all\/128-bitny-tip-v-si\/\">128-битные числа<\/a>? Сейчас расскажу.<\/p>\n<p>Коллега в офисе, который когда-то работал математиком в местном университете, наткнулся в тот свой период на интересную задачку в каком-то в иностранном журнале.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.12.20@2x.webp\" width=\"1000\" height=\"562\" alt=\"\" \/>\n<\/div>\n<p>Формулировка у неё была очень простая.<\/p>\n<p>Дано множество <tt>X<\/tt>. Единица принадлежит <tt>X<\/tt>. Если <tt>3×x<\/tt> принадлежит <tt>X<\/tt>, то и <tt>x<\/tt> принадлежит <tt>X<\/tt>. Если <tt>x<\/tt> принадлежит <tt>X<\/tt>, то и <tt>2×x + 1<\/tt> принадлежит <tt>X<\/tt>. При этом <tt>x<\/tt> — натуральное, разумеется.<\/p>\n<p>Требуется доказать, что таким образом можно получить множество всех натуральных нечётных чисел. Авторы статьи, кстати, были убеждены, что найти доказательство у читателей не получится.<\/p>\n<p>Упомянутый коллега у себя в университете при помощи студента, Фортрана и Пролога пытался проверить эту теорему по следующему алгоритму.<\/p>\n<p>Берём число, например, «9». Оно могло быть получено, согласно перечисленным утверждениям, двумя путями: либо какое-то другое нечётное число умножили на два и прибавили один, либо умножили на три. Поэтому надо проверить по формулам откуда мы могли попасть в это число, отсекая циклы и останавливаясь, когда мы попадаем в чётные числа.<\/p>\n<p>В этой задаче очень быстро растёт разрядность, поэтому в те времена рассчитать даже первую сотню чисел было достаточно проблематично.<\/p>\n<p>В общем, я решил с этой задачкой побаловаться.<\/p>\n<p>Для начала <a href=\"https:\/\/github.com\/bolknote\/odd\/blob\/main\/phase1.c\">написал программу<\/a>, которая вычисляет эти числа перебором. Начинаем, понятное дело, с единицы, следующее число получаем по формуле <tt>2×x + 1<\/tt>, потом, пока число делится на три, делим и выводим эти числа, считая для каждого ещё и  <tt>2×x + 1<\/tt> и так далее. Кроме того, пришлось ещё запоминать все найденные числа, чтобы избегать циклов.<\/p>\n<p>Первую версию я написал с использованием рекурсии, но программа падала примерно через полторы минуты работы — стек просто кончался. Пришлось переписать. Новая версия работает уже несколько часов на моём ноутбуке и вычислила за это время уже почти 17 миллионов чисел. Оставлю её на ночь, интересно, закончит ли она к утру.<\/p>\n",
            "date_published": "2024-12-20T23:55:22+05:00",
            "date_modified": "2024-12-21T13:16:27+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Fri, 20 Dec 2024 23:55:22 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "133399",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "133374",
            "url": "https:\/\/bolknote.ru\/all\/128-bitny-tip-v-si\/",
            "title": "128-битный тип в Си",
            "content_html": "<p>В одной из программ на Си мне понадобился 128-битный тип — хранить большие числа. Такой тип в некоторых компиляторах есть давно, правда, пока не было стандарта, он как только ни назывался — <tt>__int128_t<\/tt> (<i>GCC 4.1+<\/i>), <tt> __int128<\/tt> (<i>GCC 4.6+<\/i>), <tt>_ExtInt(128)<\/tt> (<i>Clang<\/i>). Теперь у нас появился стандарт Си23, где этот тип называется <tt>_BitInt(128)<\/tt>.<\/p>\n<p>Мой компилятор <tt>_BitInt(128)<\/tt> прекрасно поддерживает, казалось — счастье так близко, но…<\/p>\n<p>Каково же было моё удивление и разочарование, когда оказалось, что в языке нет ни способа вывести его на экран (у <tt>printf<\/tt> нет необходимого спецификатора формата), ни способа задать константу такого размера!<\/p>\n<p>В 2024-м году мне приходится писать собственную функцию для вывода чисел в одном из стандартных типов! А константы задавать вот так:<\/p>\n<p><tt>( (_BitInt(128) ) 0xFFFFFFFFFFFFFFFF << 64) | 0xFFFFFFFFFFFFFFFF<\/tt><\/p>\n<p>Какая-то удивительная для Си неконсистентность!<\/p>\n",
            "date_published": "2024-12-19T10:59:53+05:00",
            "date_modified": "2024-12-20T08:45:16+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Thu, 19 Dec 2024 10:59:53 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "133374",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "133255",
            "url": "https:\/\/bolknote.ru\/all\/powershoot\/",
            "title": "PowerShoot",
            "content_html": "<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.12.13@2x.webp\" width=\"1000\" height=\"100\" alt=\"\" \/>\n<\/div>\n<p>Ну что-то совсем скучно болеть дома. Мозг, конечно, почти не работает, тем не менее, чем-то развлечься хочется. Чтобы как-то себя порадовать, написал на Си мини-шутер под названием «<a href=\"https:\/\/github.com\/bolknote\/PowerShoot\"><i>PowerShoot<\/i><\/a>». Запускается из терминала, для вывода используются символы Юникода.<\/p>\n<p>Играть, правда, получится только на «МакБуке». Думаю, несложно будет адаптировать игру под терминал любой операционной системы, было бы желание.<\/p>\n<p>Геймплей простой — на вас бегут враги, надо убивать их бомбами. Игрок бросает бомбу, когда «МакБук» подключается к зарядке. Чтобы бросить две бомбы, надо подключить его, потом отключить, потом снова подключить.<\/p>\n<p>Врагов больше двух не бывает, но сложность в том, что игра реагирует на подключение зарядки с задержкой. Кроме того, можно в теории убиться собственной бомбой — пространство зациклено.<\/p>\n<p><i>Добавлено<\/i>: как справедливо заметил один из читателей, такие действия могут влиять на оставшееся количество циклов зарядки батареи ноутбука, но прошу не относиться к этой игре серьёзно, она, конечно же, сделана была в шутку, чтобы развеяться.<\/p>\n",
            "date_published": "2024-12-13T23:41:31+05:00",
            "date_modified": "2024-12-14T11:39:25+05:00",
            "tags": [
                "Игра",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Fri, 13 Dec 2024 23:41:31 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "133255",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "132928",
            "url": "https:\/\/bolknote.ru\/all\/c23\/",
            "title": "Си 23",
            "content_html": "<p>Как-то незаметно вышел Си23. В компиляторе <i>GCC 15<\/i> даже решили использовать его по-умолчанию. К слову, у языка какая-то странная нумерация. Получается, что Си89 старше, чем 23 и 99. Вам это голову не ломает?<\/p>\n<p>Посмотрел <a href=\"https:\/\/www.opennet.ru\/opennews\/art.shtml?num=62278\">список изменений<\/a>, в глаза мне бросилось следующее.<\/p>\n<p>Удалили старый синтаксис функций, где можно было описывать типы аргументов после определения функций. А я про него и <a href=\"https:\/\/bolknote.ru\/all\/algol-i-bash\/\">узнал-то<\/a> только недавно — два года назад.<\/p>\n<p>Атрибуты функций можно теперь писать в синтаксисе Си++, то есть в виде <tt>&#x5b;&#x5b;атрибут]]<\/tt>. Это очень хорошо. Так они лучше различимы, в отличие от спецификаторов, и имеют собственное пространство имён.<\/p>\n<p>Изменили действие ключевого слова <tt>auto<\/tt>. В Си оно обозначало класс хранения, причём по-умолчанию он как раз <tt>auto<\/tt>, так что в живом сишном коде я никогда это слово не видел. Теперь оно будет означать автоматический вывод типа переменной, <a href=\"https:\/\/bolknote.ru\/all\/auto-in-c-and-c-plus-plus\/\">прямо как в Си++<\/a>. Это хорошо — со временем из кода можно будет выкинуть <tt>__auto_type<\/tt>.<\/p>\n<p>Функции с пустым списком аргументов будут обрабатываться, как функции без аргументов. Это тоже хорошо, потому что раньше было неочевидно. В своё время для меня открытием было, что функции без аргументов должны в Си задаваться через синтаксис <tt>func(void)<\/tt>, правда я в коде так всё равно не делаю.<\/p>\n<p>Прекращена поддержка триграфов. Это, конечно, хорошо, но жалко. Речь идёт об альтернативных конструкциях в языке, которые были введены когда-то для поддержки некого древнего оборудования. Например, <tt>??\/<\/tt> это альтернативный синтаксис для обратного слеша. Триграфы приводили к редким багам парсинга, пугали народ, но их было весело использовать в своих проектах.<\/p>\n<p>Добавили конструкцию <tt>#embed<\/tt>, которая позволяет включать в код бинарные данные. Тоже здорово, в игре «<a href=\"https:\/\/bolknote.ru\/all\/radio-day\/\">Морской бой<\/a>», выпущенной ко Дню радио мне пришлось включать игру на Луа внутрь сишного кода внешними утилитами, а теперь можно это делать одной директивой.<\/p>\n<p>Остальные изменения тоже полезны, буду знакомиться, но эмоционального отклика не вызвали.<\/p>\n",
            "date_published": "2024-11-24T13:09:27+05:00",
            "date_modified": "2024-11-24T18:20:19+05:00",
            "tags": [
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Sun, 24 Nov 2024 13:09:27 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "132928",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "132569",
            "url": "https:\/\/bolknote.ru\/all\/99-butylok-preprocessor-si\/",
            "title": "99 бутылок: препроцессор Си",
            "content_html": "<p>Те программисты, кто каким-либо образом сталкивался с языком Си, знают, что у него есть так называемый препроцессор. Его директивы начинаются с символа решётки и выполняются до компиляции программы. Обычно его используют, чтобы включить одни файлы в другие, определить макрос или проверить значение каких-либо внутренних флагов компилятора.<\/p>\n<p>С тех пор как мне много лет назад кто-то показал как можно на нём <a href=\"https:\/\/github.com\/pfultz2\/Cloak\/wiki\/C-Preprocessor-tricks,-tips,-and-idioms\">писать программы<\/a>, меня не оставляла идея написать на нём «песню о пиве».<\/p>\n<p><b>80. Препроцессор Си<\/b>, разумеется, не задумывался как что-то на чём будут писать программы, поэтому, чтобы добиться цели, приходится использовать россыпь хитростей и разнообразных хаков. Не думаю, что имеет смысл их описывать, тем более не я их придумал. В интернете есть достаточно подробных статей по этой теме, как на <a href=\"https:\/\/www.scs.stanford.edu\/~dm\/blog\/va-opt.html\">английском<\/a>, так и на <a href=\"https:\/\/habr.com\/ru\/articles\/787442\/\">русском<\/a>.<\/p>\n<p>Изучение этих материалов было не только для меня интересным делом, но и полезным — я лучше понял как работает препроцессор в Си, а так же узнал кое-что новое, являющееся, правда, расширением языка.<\/p>\n<p>Вопреки обыкновению, вывод программы я обрабатываю другой утилитой (см. строку запуска). Это приходится делать, так как в этом языке нет простого способа вывести перевод строки. Можно было бы сделать это <i>ANSI<\/i>-кодами, но их не будет видно в листинге кода, что меня не устраивает.<\/p>\n<pre class=\"e2-text-code\"><code class=\"less\">#if 0\nBolknote.ru, 2024.11.05\nTo run:\ngcc -P -E - &lt; 99 | sed &#039;s\/\\\\n *\/\\n\/g&#039;\n#endif\n\n#define CAT(a, b) a##b\n#define SECOND(a, b, ...) b\n#define TEST(...) SECOND(__VA_ARGS__, 0)\n#define LF \\n\n\n#define DEC(n) CAT(DEC_,n)\n#define DEC_0 9\n#define DEC_1 0\n#define DEC_2 1\n#define DEC_3 2\n#define DEC_4 3\n#define DEC_5 4\n#define DEC_6 5\n#define DEC_7 6\n#define DEC_8 7\n#define DEC_9 8\n\n#define DEC_X0(a, b) IF_ELSE(ISZERO(b)) (DEC(a)) (a)\n\n#define ISEND(a, b) TEST(ISEND_ ## a ## b)\n#define ISEND_01 ,1\n\n#define ISZERO(n) TEST(ISZERO_ ## n)\n#define ISZERO_0 ,1\n\n#define ISONE(n) TEST(ISONE_ ## n)\n#define ISONE_1 ,1\n\n#define IF_ELSE(b) CAT(IF_, b)\n#define IF_0(i) ELSE_0\n#define IF_1(i) i ELSE_1\n#define ELSE_0(e) e\n#define ELSE_1(e)\n\n#define PLUR(n) IF_ELSE(ISONE(n)) (bottle) (bottles)\n\n#define PRINT_PLUR(a, b) IF_ELSE(ISZERO(a)) (b PLUR(b)) (CAT(a, b) bottles)\n\n#define PRINT_B(a, b) IF_ELSE(ISZERO(b)) \\\n        (IF_ELSE(ISZERO(a)) \\\n            (No bottles) \\\n            (PRINT_PLUR(a, b)) \\\n        ) \\\n        (PRINT_PLUR(a, b)) of beer\n\n#define PRINT_ROW(a, b) \\\n    PRINT_B(a, b) on the wall, PRINT_B(a, b)! LF \\\n    Take on down, pass it around, LF \\\n    PRINT_B(DEC_X0(a, b), DEC(b))! LF LF\n\n\n#define STOP(...) No more bottles of beer on the wall, LF \\\nNo more bottles of beer! LF \\\nGo to the store and buy some more, LF \\\nPRINT_B(9, 9) on the wall!\n\n#define EMPTY()\n#define DEFER(...) __VA_ARGS__ EMPTY()\n\n#define CROSS1(a, b) PRINT_ROW(a, b) \\\n    DEFER(IF_ELSE(ISEND(a, b)) (STOP) (CROSS2)) (DEC_X0(a, b), DEC(b))\n\n#define CROSS2(a, b) PRINT_ROW(a, b) DEFER(CROSS1) (DEC_X0(a, b), DEC(b))\n\n#define MUL0(...) MUL1(MUL1(MUL1(MUL1(__VA_ARGS__))))\n#define MUL1(...) MUL2(MUL2(MUL2(MUL2(__VA_ARGS__))))\n#define MUL2(...) MUL3(MUL3(MUL3(MUL3(MUL3(__VA_ARGS__)))))\n#define MUL3(...) __VA_ARGS__\n\n#define RUN(a, b) MUL0(CROSS1(a, b))\n\nRUN(9, 9)<\/code><\/pre>",
            "date_published": "2024-11-05T22:34:51+05:00",
            "date_modified": "2024-11-06T11:41:14+05:00",
            "tags": [
                "99",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Tue, 05 Nov 2024 22:34:51 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "132569",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "130519",
            "url": "https:\/\/bolknote.ru\/all\/operator-has-include\/",
            "title": "Оператор «__has_include»",
            "content_html": "<p>Одна штука мне очень не нравилась в программировании под «Флиппер». Фреймворк устроен так, что картинки, используемые в приложениях, должны внедряться определённым способом.<\/p>\n<p>А именно — в манифесте приложения, в директиве <tt>fap_icon_assets<\/tt> мы указываем папку, каждая картинка в которой, позже, на этапе сборки, будет преобразована в специальный массив. Этот массив всегда называется <tt>I_имя_файла_картинки<\/tt>. Массивы засовываются в файл со специальным именем, который надо импортировать в код через <tt>#include<\/tt>.<\/p>\n<p>Поскольку это происходит во время сборки, редактор (я использую «Визуал Студио Код») этот файл не видит, поэтому подчёркивает мне его импорт и все использования массивов, которые в нём описаны, как ошибку.<\/p>\n<p>Лично меня такое раздражает. В редакторе мне хочется видеть полное отсутствие ошибок.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.09.05.1@2x.jpg\" width=\"1000\" height=\"250\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Пример того как отображаются упомянутые ошибки в приложении «Пароль для Войи»<\/div>\n<\/div>\n<p>В «<a href=\"https:\/\/bolknote.ru\/tags\/hangman-game\/\">Виселице<\/a>» я с этим смирился, а сегодня каким-то чудом вспомнил про нестандартную директиву <tt> __has_include<\/tt>, которую как-то не доводилось использовать прежде. Она проверяет как раз то, что мне нужно — доступен файл для импорта или нет.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.09.05.2@2x.jpg\" width=\"1000\" height=\"172\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Реальный пример того как можно сделать, чтобы редактор не считал, что у меня ошибка<\/div>\n<\/div>\n<p>Получается, что если файл для импорта ещё не готов, то мы его не импортируем, а нужный нам массив объявляем, как имеющий внешнюю реализацию. В итоге, в редакторе ошибок не остаётся.<\/p>\n",
            "date_published": "2024-09-05T20:12:09+05:00",
            "date_modified": "2024-09-05T20:11:38+05:00",
            "tags": [
                "flipper zero",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Thu, 05 Sep 2024 20:12:09 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "130519",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "126221",
            "url": "https:\/\/bolknote.ru\/all\/konec-uskorennoy-strlen-na-flippere\/",
            "title": "Конец ускоренной strlen на «Флиппере»",
            "content_html": "<p>В итоге, эпопея с ускорением функции замера длины строки в кодировке <i>UTF-8<\/i> на «Флиппере Зеро» подошла к концу — коммит не взяли.<\/p>\n<p>У меня с самого начала этой затеи были сомнения. Я понимал, что для того, чтобы решить чью-то проблему, надо сначала доказать, что она существует, чему я должного внимания не уделил. Кроме того, сейчас в мире программирования очень важно решить проблему понятным образом, а векторное ускорение мало кому знакомо.<\/p>\n<p>Тем не менее, я с удовольствием повозился инструкциями <i>SIMD<\/i>. До этого мини-проекта у меня было теоретическое понимание о том, что это за функции, но на практике я их не использовал.<\/p>\n<p>Моя функция, кстати, претерпела некоторые изменения:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t furi_string_utf8_length(FuriString* str) {\n    size_t len = string_size(str-&gt;string);\n    const char * cstr = string_get_cstr(str-&gt;string);\n    const char* end = cstr + len;\n\n    if(len &gt;= sizeof(uint8x4_t)) {\n        const char* vend = end - sizeof(uint8x4_t);\n\n        int8x4_t zero = 0x00000000;\n        int8x4_t one = 0x01010101;\n        int8x4_t threshold = -1077952577; \/\/ -65, -65, -65, -65\n\n        do {\n            int8x4_t vec = *(int8x4_t*)cstr;\n\n            __ssub8(threshold, vec);\n            uint8x4_t result = __sel(one, zero);\n\n            len -= __usada8(result, zero, 0);\n\n            cstr += sizeof(uint8x4_t);\n        } while(cstr &lt;= vend);\n    }\n\n    while(cstr &lt; end) {\n        signed char c = *cstr++;\n        len -= c &lt; -64;\n    }\n\n    return len;\n}<\/code><\/pre><p>Оказалось, что тип <i>FuriString<\/i> хранит в себе бинарную длину строки, поэтому я переделал код, чтобы он не пытался читать чужую память. В векторизованой части это было вероятно, но своих рассуждений и <a href=\"https:\/\/github.com\/flipperdevices\/flipperzero-firmware\/pull\/3479\">дискуссии по коммиту<\/a> я сделал вывод, что в случае «Флиппера» это не имеет какого-либо значения.<\/p>\n<p>Тем не менее, если использовать бинарную длину, код получается более аккуратным и немного упрощается, так что грех не воспользоваться. Интересно бы, кстати, измерить наивный алгоритм без векторной части, но с использованием бинарной длины, наверное займусь этим в ближайшее время.<\/p>\n<p>В итоге, решая эту, возможно несуществующую, проблему, я опробовал на практике векторные инструкции расширений <i>NEON<\/i> и <i>DSP SIMD<\/i>, <a href=\"https:\/\/bolknote.ru\/all\/nado-by-dobavit-moy-strlen-vo-flipper\/\">научился делать<\/a> два параллельных запроса на вливание в «Гитхабе» и ещё раз вспомнил как компилировать и заливать свою прошивку во «Флиппер Зеро».<\/p>\n",
            "date_published": "2024-03-11T11:00:48+05:00",
            "date_modified": "2024-03-11T11:15:36+05:00",
            "tags": [
                "flipper zero",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Mon, 11 Mar 2024 11:00:48 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "126221",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125801",
            "url": "https:\/\/bolknote.ru\/all\/zamery-strlen-uft8-na-flippere-zero\/",
            "title": "Замеры strlen_uft8 на «Флиппере Зеро»",
            "content_html": "<p>Ну что же, я наконец сравнил <a href=\"https:\/\/bolknote.ru\/all\/dsp-simd-dlya-flipper-zero\/\">свой вариант<\/a> функции определения длины строки в кодировке UTF-8 с наивной реализацией и <a href=\"https:\/\/www.daemonology.net\/blog\/2008-06-05-faster-utf8-strlen.html\">чужим быстрым вариантом<\/a>.<\/p>\n<p>Оказалось, что обычные функции замера времени под «Флиппером» не работают. мне не удалось получить значение текущего времени ни одним из способов к которым я привык на Си, поэтому опять пришлось читать исходники прошивки «Флиппера».<\/p>\n<p>В итоге обнаружился метод, выводящий затраченное время с секундной точностью:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">FuriHalRtcDateTime curr_dt;\nfuri_hal_rtc_get_datetime(&amp;curr_dt);\nuint32_t start = furi_hal_rtc_datetime_to_timestamp(&amp;curr_dt);\n\nfor (int i = 0; i&lt;1000000; i++)\nlen += strlen_utf8(&quot;This is a test и русских букв тоже&quot;);\n\nfuri_hal_rtc_get_datetime(&amp;curr_dt);\nuint32_t stop = furi_hal_rtc_datetime_to_timestamp(&amp;curr_dt);<\/code><\/pre><p>Хотелось бы точнее, но этого, в принципе, хватает, чтобы понять какая функция быстрее и на сколько. Надо просто сделать очень много итераций. Я попробовал запускать на миллионе и десяти миллионах.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.02.06@2x.png\" width=\"1000\" height=\"617\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Скриншот экрана «Флиппера», снятый через специальную программу; отображается суммарная длина и время работы — пять секунд<\/div>\n<\/div>\n<p>В итоге, на десяти миллионах наивная реализация выполняется примерно за 80 секунд, моя и чужая быстрая — около 50, явного лидера из этих двух реализаций выделить не удалось. Все замеры делал несколько раз.<\/p>\n",
            "date_published": "2024-02-06T22:32:23+05:00",
            "date_modified": "2024-02-07T08:49:45+05:00",
            "tags": [
                "flipper zero",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Tue, 06 Feb 2024 22:32:23 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125801",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125765",
            "url": "https:\/\/bolknote.ru\/all\/dsp-simd-dlya-flipper-zero\/",
            "title": "DSP SIMD для Flipper Zero",
            "content_html": "<p style=\"background: rgb(222, 235, 217); padding: 10px; color:#000\">В этом версии нет никакой защиты цикла от чтения за границей буфера, мне показалось, что во «Флиппере» это неважно, но возможно это не так.<\/p>\n<p>Ну что ж, разобрался я что за зверь такой этот <i>DSP SIMD<\/i> и запрограммировал на нём векторизированную версию функции для измерения длины строки.<\/p>\n<p>Сегодня описывать и замерять уже не буду, и так времени много ушло, но по крайней мере всё работает на настоящем «Флиппере».<\/p>\n<p>Сразу можно заметить, что вектора тут вдвое короче — всего четыре байта, да и функций для работы с ними гораздо меньше, но этого набора оказалось достаточно, чтобы всё векторизовать.<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">#include &lt;arm_acle.h&gt;\n\nsize_t strlen_arm_utf8(char * str) {\n    uint32_t len = 0;\n    int8x4_t zero = 0x00000000;\n    int8x4_t one  = 0x01010101;\n    int8x4_t threshold = -1077952577; \/\/ -65, -65, -65, -65\n\n    for (;;) {\n        uint8x4_t result;\n        int8x4_t vec = *(int8x4_t *) str;\n\n        __uadd8(0xFFFFFFFF, vec); result = __sel(zero, one);\n        if (result &gt; 0) {\n            if (result == 0x01000000) {\n                __ssub8(threshold, vec); result = __sel(zero, one);\n                return __usada8(result, 1, len);\n            }\n            break;\n        }\n\n        __ssub8(threshold, vec); result = __sel(zero, one);\n        len = __usada8(result, zero, len);\n\n        str += sizeof(uint8x4_t);\n    }\n\n    for (signed char c; (c = *str); str++) {\n        if (c &gt;= -64) {\n            len++;\n        }\n    }    \n\n    return len;\n}<\/code><\/pre>",
            "date_published": "2024-02-04T21:47:15+05:00",
            "date_modified": "2024-02-26T15:29:20+05:00",
            "tags": [
                "flipper zero",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Sun, 04 Feb 2024 21:47:15 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125765",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125762",
            "url": "https:\/\/bolknote.ru\/all\/arm-byvayut-raznye\/",
            "title": "ARM бывают разные",
            "content_html": "<p>Преодолев <a href=\"https:\/\/bolknote.ru\/all\/pora-perenesti-izmerenie-dliny-na-flipper\/\">внутреннее сопротивление<\/a>, я всё-таки занялся переносом <a href=\"https:\/\/bolknote.ru\/all\/chut-bolee-bystry-podschyot-dliny-stroki-v-utf-8\/\">векторизованной функции измерения длины строки<\/a> на «Флиппер Зеро». Почти сразу выяснилось, что моя интуиция меня не обманула — на этом пути куча проблем.<\/p>\n<p>Во «Флиппере» стоит процессор <i>ARM<\/i>, а они, как оказалось, бывают очень разные.<\/p>\n<p>Я ещё в самом начале посмотрел спецификацию процессора «Флиппера» и, увидев слово <i>SIMD<\/i>, совершенно успокоился — в моём понимании это означало, что я могу использовать любые команды векторизации, которые мне необходимы. Тут я немного поторопился, но меня подгоняло желание разобраться как устроена векторизация. Теорию я знал, а тут подвернулась, пусть немного синтетическая, но задача, которую руки чесались решить.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.02.04@2x.jpg\" width=\"1000\" height=\"339\" alt=\"\" \/>\n<div class=\"e2-text-caption\">Некоторые ошибки компиляции упомянутой функции под «Флиппер Зеро»<\/div>\n<\/div>\n<p>Как я уже говорил, мне захотелось срезать углы, поэтому функцию до этого момента я писал на своём ноутбуке, раз так случилось, что у меня на нём тоже процессор <i>ARM<\/i>. И лишь сегодня я попробовал вставить её в одну из своих программ и собрать её для «Флиппера».<\/p>\n<p>Сначала компилятор заявил мне, что не нашёл некоторые векторные функции, в частности функцию нахождения минимума <tt>vminvq_u8<\/tt> и суммы <tt>vaddvq_u8<\/tt>.<\/p>\n<p>Ветку с <tt>vminvq_u8<\/tt> я временно убрал, а функцию суммы <tt>vaddvq_u8<\/tt> решил заменить на поэлементное сложение (<tt>vaddq_u8<\/tt>), которая, вроде, компилятору знакома.<\/p>\n<p>Мысль простая — в векторах, сумму которых мы находим, на каждой позиции нули и единицы, поскольку каждый элемент вектора у нас восьмибитный, в общем случае у нас поместится результат 255 операций, а потом я могу просто просуммировать элементы циклом. Как-то так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">uint8x16_t len_v = vdupq_n_u8(0);\nint reset = 0;\n\n\/\/ тут у нас цикл\nuint8x16_t gt = vcgtq_s8(operand, threshold);\nlen_v = vaddq_u8(vandq_u8(gt, delta), len_v);\n\nif (++reset &gt;= 255) {\n    for (size_t i = 0; i &lt; sizeof(uint8x16_t); i++) {\n        len += *((uint8_t *)&amp;len_v + i);\n    }\n\n    reset = 0;\n    len_v = vdupq_n_u8(0);\n}<\/code><\/pre><p>Я был очень доволен решением (оно не совсем моё, его мне навеял код, используемый в <i>PHP<\/i>), но оказалось, что оно тоже не компилируется.<\/p>\n<p>Сборка заругалась на ошибку <i>target specific option mismatch<\/i>, означающую, что указанное при компиляции железо не поддерживает те возможности, которые я пытаюсь использовать в коде. Пришлось лезть в тулчейн, чтобы разобраться какие ключи компиляции сейчас используются.<\/p>\n<p>В файле <tt>compile_commands.json<\/tt> можно увидеть, что при сборке используется ключ <tt>-mfpu=fpv4-sp-d16<\/tt>, тогда как мне, чтобы использовать нужные команды векторизации нужен ключ <tt>-mfpu=neon<\/tt>.<\/p>\n<p>Так как ключи компиляции указывали разработчики «Флиппера», думаю, они в точности знали в какие значения их выставить. Согласно <a href=\"https:\/\/developer.arm.com\/Processors\/Cortex-M4\">спецификации<\/a> на борту использованного во «Флиппере» процессора <i>SIMD<\/i> есть, но, видимо, какой-то другой. Так как <i>SIMD<\/i> написано в разделе <i>DSP Extension<\/i>, надо смотреть что это такое, может всё-таки можно использовать эти команды для ускорения.<\/p>\n",
            "date_published": "2024-02-04T14:25:12+05:00",
            "date_modified": "2024-02-04T14:53:47+05:00",
            "tags": [
                "flipper zero",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Sun, 04 Feb 2024 14:25:12 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125762",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125662",
            "url": "https:\/\/bolknote.ru\/all\/chut-bolee-bystry-podschyot-dliny-stroki-v-utf-8\/",
            "title": "Чуть более быстрый подсчёт длины строки в UTF-8",
            "content_html": "<p>Сегодня очень плохо спал — всё время просыпался, потом долго ворочался, не мог уснуть. Утром оказалось мозг никак не мог успокоиться после <a href=\"https:\/\/bolknote.ru\/all\/razbor-bystrogo-podschyota-dliny-stroki-v-utf-8\/\">вчерашней заметки<\/a> про разбор быстрого алгоритма для подсчёта длины строки в <i>UTF-8<\/i> при помощи векторизации для процессоров <i>ARM<\/i>.<\/p>\n<p>Вчера я остановился на том, что у разбираемого способа есть две очевидные недоработки — ненужное вычисление, которое решается инвертированием условия и подсчёт «хвоста», который всегда вычисляется «классическим» способом — без векторизации, хотя если строка у нас кратна вектору, можно было бы этого избежать.<\/p>\n<p>Первый недостаток исправлялся очень просто, а хорошая реализация второй проверки мне вчера в голову не пришла.<\/p>\n<p>Зато, когда я проснулся, она уже была у меня в голове. Всего три операции — первой получаем вектор, где на позициях с ненулевым байтом записывается 255, потом делается операция «И» с номерами позиций и весь вектор суммируется.<\/p>\n<p>В итоге, если ноль стоит в векторе только на крайней правой позиции, у нас получается сумма арифметической прогрессии без последнего элемента.<\/p>\n<p>По замерам такой вариант чуть быстрее.<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_utf8(unsigned char * p) {\n    size_t len = 0;\n\n    const int8x16_t threshold = vdupq_n_s8(-63);\n    const uint8x16_t delta = vdupq_n_u8(1);\n\n    for(;;) {\n        int8x16_t operand = vld1q_s8((const int8_t * ) p);\n        if (vminvq_u8(operand) == 0) {\n            uint8x16_t pos = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};\n            uint8_t sum = vaddvq_u8(vandq_u8(vtstq_u8(operand, operand), pos));\n\n            \/\/ арифметическая прогрессия без последнего члена\n            if (sum == 16*(16+1) \/ 2 - 16) {\n                uint8x16_t gt = vcgtq_s8(operand, threshold);\n                return len + vaddvq_u8(vandq_u8(gt, delta)) - 1;\n            } else {\n                break;\n            }\n        }\n\n        uint8x16_t gt = vcgtq_s8(operand, threshold);\n        len += vaddvq_u8(vandq_u8(gt, delta));\n        p += sizeof(uint8x16_t);\n    }\n\n    for (signed char c; (c = *p); p++) {\n        if (c &gt;= -64) {\n            len++;\n        }\n    }\n\n    return len;\n}<\/code><\/pre>",
            "date_published": "2024-01-28T10:42:29+05:00",
            "date_modified": "2024-02-05T23:39:57+05:00",
            "tags": [
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Sun, 28 Jan 2024 10:42:29 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125662",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125661",
            "url": "https:\/\/bolknote.ru\/all\/razbor-bystrogo-podschyota-dliny-stroki-v-utf-8\/",
            "title": "Разбор быстрого подсчёта длины строки в UTF-8",
            "content_html": "<p>Давайте попробуем всё-таки разобраться как работает <a href=\"https:\/\/bolknote.ru\/all\/utf-8-na-arm\/\">быстрое вычисление<\/a> длины строки в кодировке <i>UTF-8<\/i>. В <a href=\"https:\/\/bolknote.ru\/all\/opredelyaem-granicu-simvola-v-utf-8\/\">прошлый раз<\/a> мы узнали, что для этого нам надо подсчитать все байты, которые будут больше или равны −64.<\/p>\n<p>Напомню как выглядел код:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_php(unsigned char * p) {\n    size_t len = 0;\n\n    const int8x16_t threshold = vdupq_n_s8(-64);\n    const uint8x16_t delta = vdupq_n_u8(1);\n\n    for(;;) {\n        int8x16_t operand = vld1q_s8((const int8_t * ) p);\n        if (vminvq_u8(operand) == 0) {\n            break;\n        }\n\n        uint8x16_t lt = vcltq_s8(operand, threshold);\n        len += sizeof(uint8x16_t) - vaddvq_u8(vandq_u8(lt, delta));\n        p += sizeof(uint8x16_t);\n    }\n\n    for (signed char c; (c = *p); p++) {\n        if (c &gt;= -64) {\n            len++;\n        }\n    }\n\n    return len;\n}<\/code><\/pre><p>Для ускорения подсчёта в алгоритме используется векторизация, — это когда мы работаем с пачкой чисел, делая над каждым из них какое-то действие за одну операцию. В данном случае используется вектор 8×16 — шестнадцать значений по восемь бит (типы <tt>uint8x16_t<\/tt> и <tt>int8x16_t<\/tt>).<\/p>\n<p>Вектор загружается функцией <tt>vld1q_s8<\/tt>, где коды символов считываются по адресу, записанному в указателе <tt>p<\/tt>. В дальнейшем мы будем его сдвигать на размер вектора, чтобы забирать следующую пачку данных.<\/p>\n<p>Ниже используется функция <tt>vminvq_u8<\/tt>, которая вычисляет минимальное значение в векторе, рассматривая его как набор байтов без знака. Если там ноль, значит строка кончилась и в нашем векторе — хвост, который надо досчитать обычным способом, без вектора. Тут можно было бы проверить на какой позиции слева находится признак конца строки, если на самой последней, то строка была кратна вектору. <s>Но я не стал заморачиваться.<\/s> Но такая проверка оказалась медленнее подсчёта длины хвоста обычным способом.<\/p>\n<p>Дальше функция <tt>vcltq_s8<\/tt> сравнивает каждый байт в векторе со значением −64  — это значение загружается в каждый элемент вектора для сравнения функцией <tt> vdupq_n_s8<\/tt>.<\/p>\n<p>Результирующий вектор заполняется значениями 0 или 255, в зависимости от результата сравнения. Функция <tt>vandq_u8<\/tt> производит операцию «И» с единицей, после чего у нас в векторе ноль остаётся неизменным, а 255 превращается в единицу. Эти единицы мы суммируем функцией <tt>vaddvq_u8<\/tt>.<\/p>\n<p>Теперь у нас получился вектор, в котором подсчитаны все внутренние байты символов, а чтобы подсчитать внешние, надо вычесть это число из длины вектора. Правильнее было бы инвертировать сравнение, до этого в разгаре экспериментов просто не дошли руки.<\/p>\n<p>Полученное число мы добавляем к вычисляемой длине и сдвигаем указатель. В конце досчитываем хвост обычным циклом. Конец алгоритма.<\/p>\n",
            "date_published": "2024-01-28T00:51:57+05:00",
            "date_modified": "2024-01-28T01:47:41+05:00",
            "tags": [
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Sun, 28 Jan 2024 00:51:57 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125661",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125624",
            "url": "https:\/\/bolknote.ru\/all\/opredelyaem-granicu-simvola-v-utf-8\/",
            "title": "Определяем границу символа в UTF-8",
            "content_html": "<p>В Телеграме попросили рассказать как работает код, который я <a href=\"https:\/\/bolknote.ru\/all\/utf-8-na-arm\/\">приводил вчера<\/a> в заметке про ускорение функции, возвращающей количество символов в строке с кодировкой <i>UTF-8<\/i>.<\/p>\n<p>Там действительно есть что рассказать, но рассказ придётся разбить на несколько частей. Я постараюсь по возможности не погружаться в дебри. В этой части начну с того, что расскажу чем вообще необычна эта кодировка.<\/p>\n<p>Все кодировки условно можно разделить на две части: кодирующие символ фиксированным или переменным количеством байт. К первым можно отнести, например, <i>CP1251<\/i>, когда-то широко используемую в ОС «Виндоуз» — там символ кодировался одним байтом, ко вторым — <i>UTF-8<\/i>, где один символ может занимать от одного до четырёх байт.<\/p>\n<p>Справедливости ради, есть и <a href=\"https:\/\/bolknote.ru\/all\/3283\/\">другие виды кодирования<\/a>, но нам их рассматривать ни к чему.<\/p>\n<p>Тут важно то, что для строки в <i>UTF-8<\/i>, нельзя получить её длину, подсчитав количество байт и разделив их на коэффициент хранения. Для каждого байта нам надо понять что он кодирует и считать только те байты, которые отвечают за начало символа. Как это сделать?<\/p>\n<p>Посмотрим на картинку из «<a href=\"https:\/\/ru.wikipedia.org\/wiki\/UTF-8\">Википедии<\/a>», которая показывает как устроена эта кодировка:<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.01.25@2x.png\" width=\"727\" height=\"180\" alt=\"\" \/>\n<\/div>\n<p>Тут нам интересна только колонка «шаблон», где видно, что все «внутренние» байты символов начинаются с одной и той же сигнатуры — старший бит установлен, младшие — сброшены.<\/p>\n<p>То есть, байты в диапазоне от <tt>10000000<\/tt> до <tt>10111111<\/tt> (в бинарном представлении) — внутренние байты, остальные — начало символа. Опять же, согласно таблице начала попадают в диапазоны <tt>00000000<\/tt>-<tt>01111111<\/tt> и от <tt>11000000<\/tt> до <tt>11011111<\/tt>.<\/p>\n<p>Умные люди заметили одну закономерность, сейчас мы её тоже увидим. Давайте напишем небольшую программу:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">#include &lt;stdio.h&gt;\n\nint main() {\n    const signed char arr[] = {\n        0b10000000, 0b10111111, 0b00000000, 0b01111111, 0b11000000, 0b11011111\n    };\n\n    for (int i = 0; i &lt; sizeof(arr) \/ sizeof(arr[0]); i++) {\n        for (int b = 128; b; b &gt;&gt;= 1) {\n            printf(&quot;%d&quot;, (arr[b] &amp; i) &gt; 0);\n        }\n\n        printf(&quot; % d\\n&quot;, arr[i]);\n    }\n}<\/code><\/pre><p>Вот что она выводит (диапазон, в котором находятся внутренние байты, я выделил другим цветом):<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/bolknote.ru\/pictures\/2024.01.25.1@2x.png\" width=\"727\" height=\"159\" alt=\"\" \/>\n<\/div>\n<p>Закономерность в том, что все внутренние байты в знаковом представлении по значению меньше −64. Это довольно дешёвая проверка с точки зрения машинного кода, что очень хорошо, так как наша цель — производительность.<\/p>\n<p>Про остальное — в следующий раз.<\/p>\n",
            "date_published": "2024-01-25T14:04:08+05:00",
            "date_modified": "2024-01-28T00:07:56+05:00",
            "tags": [
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Thu, 25 Jan 2024 14:04:08 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125624",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        },
        {
            "id": "125614",
            "url": "https:\/\/bolknote.ru\/all\/utf-8-na-arm\/",
            "title": "UTF-8 на ARM",
            "content_html": "<p>Пока проект внедрения Юникода во «Флиппер Зеро» на паузе, — разработчики занимаются обновлением одной из важных библиотек, я вспомнил, что вообще-то в природе существуют разные способы ускорения обработки строки в кодировке <i>UTF-8<\/i>. Именно её я выбрал для хранения строк. А поскольку железо у «Флиппера» весьма ограниченное, может быть имеет смысл подумать как сэкономить ресурсы.<\/p>\n<p>Например, когда-то, ещё во времена работы в «Яндексе», я <a href=\"https:\/\/bolknote.ru\/all\/2855\/\">сравнивал<\/a> между собой различные реализации функции <tt>strlen<\/tt>, где ускорение достигалось за счёт векторизации.<\/p>\n<p>Любопытно посмотреть как обстоят дела с ускорением таких вещей на процессоре, который установлен на «Флиппере». Правда до него у меня руки ещё не дошли, но я уже потренировался на своём «Макбуке», благо тут процессор той же архитектуры.<\/p>\n<p>Идея всё та же — векторизация. За референс я взял наивную реализацию с перебором по одному байту:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_naive(const char* str) {\n    size_t len = 0;\n    char ch;\n\t\n    while ((ch = *str++)) {\n        len += (ch &amp; 0b11000000) != 0b10000000;\n    }\n\n    return len;\n}<\/code><\/pre><p>Попробовал несколько вариантов, но лучше всех показали себя две функции: та, которую я упоминал в первом абзаце и переделанная нашими общими с братишкой усилиями <a href=\"https:\/\/github.com\/php\/php-src\/blob\/ea3c541640a75340ea06c460cf282bde5bf75b13\/ext\/mbstring\/mbstring.c#L1772\">реализация<\/a>, найденная в недрах <i>PHP<\/i>.<\/p>\n<p>После переделки она стала выглядеть так:<\/p>\n<pre class=\"e2-text-code\"><code class=\"cpp\">size_t strlen_php(unsigned char * p) {\n    size_t len = 0;\n\n    const int8x16_t threshold = vdupq_n_s8(-64);\n    const uint8x16_t delta = vdupq_n_u8(1);\n\n    for(;;) {\n        int8x16_t operand = vld1q_s8((const int8_t * ) p);\n        if (vminvq_u8(operand) == 0) {\n            break;\n        }\n\n        uint8x16_t lt = vcltq_s8(operand, threshold);\n        len += sizeof(uint8x16_t) - vaddvq_u8(vandq_u8(lt, delta));\n        p += sizeof(uint8x16_t);\n    }\n\n    for (signed char c; (c = *p); p++) {\n        if (c &gt;= -64) {\n            len++;\n        }\n    }\n\n    return len;\n}<\/code><\/pre><p>Повторюсь, у меня пока не дошли руки попробовать это всё на «Флиппере», но на моём «Макбуке» результаты выглядят следующим образом. Это десять тысяч прогонов на русскоязычном тексте, размер которого чуть больше ста тысяч символов.<\/p>\n<pre class=\"e2-text-code\"><code class=\"plaintext\">Name   Length  Time\n----   ------  ----\nPHP    117465   97797\nFast   117465  143780\nNaïve  117465  531535<\/code><\/pre><p>Не скажу, что я делал всё как положено, — надо бы считать среднее и отклонение, разносить по разным файлам, научиться прибивать процесс к производительным ядрам, но разница в результатах достаточно показательна (я запускал несколько десятков раз), поэтому, как мне кажется, и так нормально.<\/p>\n<p>Цифры в попугаях (больше — хуже), которые выдаёт сишная функция <tt>clock()<\/tt>, компилировал компилятором <i>Clang 15<\/i> с ключом <tt>-O3<\/tt>.<\/p>\n",
            "date_published": "2024-01-24T22:13:28+05:00",
            "date_modified": "2024-01-28T00:41:04+05:00",
            "tags": [
                "flipper zero",
                "utf8",
                "программирование",
                "си"
            ],
            "author": {
                "name": "Евгений Степанищев",
                "url": "https:\/\/bolknote.ru\/",
                "avatar": "https:\/\/bolknote.ru\/pictures\/userpic\/userpic@2x.jpg?1760600028"
            },
            "_date_published_rfc2822": "Wed, 24 Jan 2024 22:13:28 +0500",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "125614",
            "_rss_enclosures": [],
            "_e2_data": {
                "is_favourite": false,
                "links_required": null,
                "og_images": []
            }
        }
    ],
    "_e2_version": 4079,
    "_e2_ua_string": "Aegea 11.0 (v4079e)"
}