tgindex
Бестиарий программирования

Бестиарий программирования

Статистика

Наблюдения за жизнью ошибок в коде. Андрей Карпов. ГОСТ Р 71207-2024, ГОСТ Р 56939-2024, РБПО, Статический анализ кода Канал-дублёр в MAX: https://max.ru/join/3VWTp9apkQvTMSRQ__LGiTQ5NGVBj8p_tOpwlQO6vS8

Последний пост
13 авг.
Последнее чтение
23:43
Постов за неделю
7
Всего постов
37
Тип
открытый
Язык
русский
Категория
Технологии
В каталоге с
12 авг.
Подписчики
1 138
−3 за 4 дн.
Сутки
0
0,00%
Неделя
 
Месяц
 
Просмотров на пост
401
37 постов
Вовлечённость
35,2%
к подписчикам
Постов в день
1,0
всего 37
Упоминаний
1
каналов
Охват размещения
оценка
1/24сутки в ленте
162
1/48двое суток
185
1/72трое суток
200

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • Раздутый C++ код (часть 2 из 2) А вот другой пухлый код, где PVS-Studio выдаёт сразу три предупреждения: • V547 [CWE-570] Expression 'is_empty' is always false. tensor_bindings.cc 3007 • V547 [CWE-570] Expression 'print_size' is always false. tensor_bindings.cc 3015 • V547 [CWE-571] Expression '!parts.empty()' is always true. tensor_bindings.cc 3023 Код, на который выданы предупреждения, на первый взгляд умный, с массивом, с циклом... А если присмотреться — лабуда. bool is_empty = false; // handled above; always false here bool print_size = is_empty && (self.sizes().size() != 1); bool suppress_dtype_non_empty = (!is_empty) &&   (self.dtype() == ScalarType::Float32 ||    self.dtype() == ScalarType::Int64 ||    self.dtype() == ScalarType::Bool); bool print_dtype = !suppress_dtype_non_empty; if (is_empty) {   // For empty tensors, only print dtype when dtype != default float32   print_dtype = (self.dtype() != ScalarType::Float32); }   std::string out = "tensor("; out += body; std::vector<std::string> parts; if (print_size) {   parts.push_back(std::string("size=") + format_sizes(self.sizes())); } if (print_dtype) {   parts.push_back(std::string("dtype=") + dtype_name(self.dtype())); } // Always include device suffix for CUDA tensors parts.push_back(std::string("device='cuda:") +                 std::to_string((int)self.device().index) + "'"); if (!parts.empty()) {   out += ", ";   for (std::size_t i = 0; i < parts.size(); ++i) {     if (i) out += ", ";     out += parts[i];   } } out += ")"; return out; Если присмотреться получше, то всё это можно сократить в три раза: std::string out = "tensor(" + body + ", ";   if (self.dtype() != ScalarType::Float32 &&     self.dtype() != ScalarType::Int64 &&     self.dtype() != ScalarType::Bool) {   out += std::string("dtype=") + dtype_name(self.dtype()) + ", "; } out += "device='cuda:" + std::to_string((int)self.device().index) + "')"; return out; Вот что здесь интересно: если рассуждать об анализе исходного варианта, то вроде как ошибок не находится. Код сложный, правильно работает, выхода за границу массива нет. Почтение к ИИ. Когда же код схлопывается до своей сути, то понимаешь, что там и ошибаться то негде. Он просто написан длиннее. Не к чему тут относиться с почтением. Итого: нет в проекте никаких настоящих 100,000 строк С++ кода. Думаю, что если вынести дубликаты в функции и провести рефакторинг, количество кода сократится раз в 5. Проект на 20,000 строк кода — это баловство. Вот и вижу в нём не ошибки, а проблему раздутого кода и предупреждения анализатора про большое количество ложных/истинных условий и т.п.

  • Раздутый C++ код (часть 1 из 2) Есть такая старая программистская байка, что нельзя платить программистам за строки кода, так как тогда они будут писать длинный бестолковый код и любить метод copy-paste. Будущее наступило. Этими "программистами" является генеративный ИИ (GenAI), которому как раз платят за строки кода. Я проверил VibeTensor с помощью статического анализатора PVS-Studio, а также посмотрел С++ код глазами. Было интересно узнать, как много ошибок в нём можно найти с помощью классического обзора кода и статического анализа. Так вот, у меня нет ответа на этот вопрос. Непонятно, потому что главная проблема этого кода в том, что он ужасно раздут. Это сильно мешает его обзору. Мне тяжело продираться сквозь это болото, а вместе со мной "вязнет" и статический анализатор. Впрочем, ожидать большого количества ошибок здесь тоже не стоит: по-настоящему полезного кода в этом проекте кот наплакал. Как же так? Проект вроде не такой уж маленький. Проанализированных C++ файлов более 400, а количество строк кода — около 100,000. Да, но это только кажется, что там есть что смотреть и анализировать. Повторюсь: основная проблема качества этого кода — его избыточность. Она выражена как в постоянном повторении блоков кода, так и просто в бессмысленных лишних действиях, растягивающих код. Раньше бы сказали, что этот проект писался методом copy-paste. В данном случае это не так, но генерация кода приводит ровно к таким же последствиям. Вместо выноса обобщённой функциональности в функции, вновь и вновь генерируется код для решения схожим проблем. Например, я уже писал в статье "C++: Пиши, сокращай, оптимизируй", про блок кода, который встречается 9 раз и при этом сам растянут. Скоро напишу другую статью, а пока рассмотрю ещё один пример.

  • SAST и DAST спешат на помощь (часть 3 из 3) При этом такую ошибку можно моментально обнаружить с помощью статического или динамического анализа. Статический анализатор PVS-Studio сразу предупреждает об аномалии в коде с помощью сообщения: V742 Function receives an address of a 'char' type variable instead of pointer to a buffer. Inspect the first argument. Или достаточно воспользоваться динамическим анализом. AddressSanitizer (gcc ключ: fsanitize=address). Он сразу покажет проблему уже на первом самом простом тесте Test1. Юнит-тесты и динамический анализ работают в паре. Санитайзер обнаруживает проблему на этапе запуска юнит-теста. Не будет теста — ошибка всплывёт гораздо позже. Заключение Статический и динамический анализ хорошо дополняют юнит-тесты и другие методы выявления ошибок. При этом нет какого-то лучшего метода или инструмента. Статические и динамические анализаторы имеют свои слабые стороны и дополняют друг друга. В свою очередь, юнит-тесты могут выявить ошибки в логике, где, скорее всего, будут бессильны анализаторы. Используйте всё. По началу это потребует вложений, но со временем окупит себя ранним выявлением большого процента багов на самых ранних этапах разработки. Чем раньше ошибка обнаружена, тем проще и дешевле её исправление (подход shift-left testing).

  • SAST и DAST спешат на помощь (часть 2 из 3) Можно написать юнит-тесты типа таких: void Test1() {     char *a = "a";     char *b = "";     char *q;     _lfortran_strcat(&a, &b, &q);     int ok = strcmp(q, "a") == 0;     printf("%s+%s=%s %s\n", a, b, q, ok ? "ok" : "err");     free(q); }   void Test2() {     char *a = "12";     char *b = "345";     char *q;     _lfortran_strcat(&a, &b, &q);     int ok = strcmp(q, "12345") == 0;     printf("%s+%s=%s %s\n", a, b, q, ok ? "ok" : "err");     free(q); } И ничего не заметить. Тесты проходят успешно: a+=a ok 12+345=12345 ok Тесты на коротких строках не выявят проблему. В голову может и не прийти мысль попробовать работать с длинными строками. Зачем? На первый взгляд такие тесты ничего не дают. Скорее всего, будут созданы тесты на краевые случаи (пустые строки), но они нерелевантные для поиска обсуждаемого бага. При этом даже с длинными строками, вероятность заскочить в соседний блок всего 1 к N (где N, например 16). Можно объединить две огромные строки из 111111 символов и всё будет хорошо, ведь конец результирующей строки (222222 символов) не лежит на границе 16-байтного блока. Ещё юнит-тесты Только не подумайте, что я критикую юнит-тесты. Это замечательная штука! Однако бывают ошибки, которым легко от этих юнит-тестов спрятаться. И перед нами как раз такой случай. Дело в том, что легко не заметить, даже когда будет пересекаться тот невидимый рубеж выделенного блока памяти. Следующий тест создаёт не такие уж короткие строки длинной 37 символов. Как думаете, такой тест приведёт к падению программы или ещё чему-то? void Test3() {     char *a = "123";     for (unsigned i = 1; i != 35; ++i)     {         char *b = (char *)malloc(i + 1);         memset(b, 'a', i);         b[i] = '\0';         char *q;         _lfortran_strcat(&a, &b, &q);         int ok = strlen(q) == 3 + i;         printf("%u %s %s\n", i, q, ok ? "ok" : "err");         free(b);         free(q);     } } Приведёт или нет — неизвестно, ведь тут неопределённое поведение. Но на практике я собираю его gcc с ключом -O2 и не наблюдаю какого-то проявления ошибки, хотя, по идее, блоки памяти уже испорчены. Но по тесту всё ещё кажется, что всё нормально: 1 123a ok 2 123aa ok 3 123aaa ok 4 123aaaa ok 5 123aaaaa ok 6 123aaaaaa ok 7 123aaaaaaa ok 8 123aaaaaaaa ok 9 123aaaaaaaaa ok 10 123aaaaaaaaaa ok 11 123aaaaaaaaaaa ok 12 123aaaaaaaaaaaa ok 13 123aaaaaaaaaaaaa ok 14 123aaaaaaaaaaaaaa ok 15 123aaaaaaaaaaaaaaa ok 16 123aaaaaaaaaaaaaaaa ok 17 123aaaaaaaaaaaaaaaaa ok 18 123aaaaaaaaaaaaaaaaaa ok 19 123aaaaaaaaaaaaaaaaaaa ok 20 123aaaaaaaaaaaaaaaaaaaa ok 21 123aaaaaaaaaaaaaaaaaaaaa ok 22 123aaaaaaaaaaaaaaaaaaaaaa ok 23 123aaaaaaaaaaaaaaaaaaaaaaa ok 24 123aaaaaaaaaaaaaaaaaaaaaaaa ok 25 123aaaaaaaaaaaaaaaaaaaaaaaaa ok 26 123aaaaaaaaaaaaaaaaaaaaaaaaaa ok 27 123aaaaaaaaaaaaaaaaaaaaaaaaaaa ok 28 123aaaaaaaaaaaaaaaaaaaaaaaaaaaa ok 29 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok 30 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok 31 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok 32 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok 33 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok 34 123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ok Падение случится при константе 38 в цикле for (unsigned i = 1; i != 38; ++i): free(): invalid pointer Program terminated with signal: SIGSEGV Не надо искать какой-то особый смысл в числе 38, просто так получилось. Интересен момент, как долго ошибка пряталась от юнит-тестов.

  • SAST и DAST спешат на помощь (часть 1 из 3) Никто не спорит о пользе статического или динамического анализа кода. Но некоторые разработчики воспринимают эту пользу как абстрактную и останавливаются на уровне написания юнит-тестов. Сейчас вспомнился один пример из мира C, который хорошо показывает, что юнит-тесты иногда плохо помогают находить даже самые типовые ошибки. Баг, который я сейчас покажу, я уже рассматривал в статье "Красивая ошибка в реализации функции конкатенации строк". В проекте LFortran была вот такая функция для конкатенации (объединения) двух строк в новом буфере: void _lfortran_strcat(char** s1, char** s2, char** dest) {     int cntr = 0;     char trmn = '\0';     int s1_len = strlen(*s1);     int s2_len = strlen(*s2);     int trmn_size = strlen(&trmn);     char* dest_char = (char*)malloc(s1_len+s2_len+trmn_size);     for (int i = 0; i < s1_len; i++) {         dest_char[cntr] = (*s1)[i];         cntr++;     }     for (int i = 0; i < s2_len; i++) {         dest_char[cntr] = (*s2)[i];         cntr++;     }     dest_char[cntr] = trmn;     *dest = &(dest_char[0]); } Здесь классическая ошибка, когда выделяемый буфер на 1 байт меньше необходимого. Не учтён терминальный ноль. Вернее, учтён, но его размер вычисляется неправильно. char trmn = '\0'; int trmn_size = strlen(&trmn); Здесь символ trmn интерпретируется как пустая строка. Её длина нулевая. Соответственно, переменная trmn_size, название которой хранит размер терминального нуля, всегда будет равна 0. В результате терминальный ноль будет записан уже за пределами выделенного буфера. Ошибка простая и понятная. Выход за границу буфера в программе на C это вообще типовая проблема. Что тут ещё обсуждать? Ошибка найдена, дело закрыто. Меня заставил задуматься комментарий читателя о коварности этой ошибки из-за гранулярности выделяемой памяти. Функция malloc на самом деле выделяет не столько памяти, сколько у неё просят. Она запрашивает у операционной системы большие блоки и нарезает их на куски, добавляя служебную информацию. Точный размер блока зависит от конкретной реализации. Даже если вы запросите malloc(1), аллокатор все равно вернёт указатель на блок размером, скажем, 32 байта (минус служебные поля — вам достанется 24 полезных байта). Это делается для обеспечения выравнивания памяти (обычно 16-байтного) и упрощения менеджера памяти. Возвращаемый указатель должен быть выровнен. Поскольку функция malloc ничего не знает о том, какие типы будут храниться в выделенной памяти, она ориентируется на самое большое выравнивание, которое может потребоваться. На практике (x86-64) malloc выдаёт адреса, кратные 16. Поэтому размер блока всегда округляется вверх до следующей границы выравнивания. Я описал всё очень поверхносно и приблизительно. Важно то, что на практике в рассмотренном коде обычно будет выделяться памяти больше, чем требуется! Если блоки кратны 16 байтам, то запись терминального нуля в соседний блок случится, только если результирующая строка также кратна 16 байтам. Другими словами, вероятность, что ошибка проявит себя, равна 1 к 16 (или не 16 — это число взято как одно из возможных). Тут следует сразу сделать оговорку про неопределённое поведение. Формально код в любом случае некорректен, так как содержит выход за границу массива. Нельзя рассуждать, как он будет работать, как ошибка проявит себя и т.д. Однако неопределённое поведение — это в том числе ситуация, когда неправильный код работает так, как ожидалось. Рассматриваемая ситуация, когда из-за особенностей работы менеджера памяти этой памяти выделяется больше, как раз и может привести к видимости, что всё работает хорошо.

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

  • 10 авг.29571из pvsstudio_rus

    У нас еще одна крутая новость! Мы запускаем новый цикл вебинаров "Надёжность, качество, безопасность ПО: методология и инструменты" ⚡️ В предыдущем цикле вебинаров "Вокруг РБПО" мы рассмотрели 25 процессов из ГОСТ Р 56939—2024, направленных на создание безопасных программных решений. Теперь поговорим не только о безопасности, но и в целом о создании качественного и надёжного ПО. Вместе с экспертами из разных компаний мы поговорим про развитие DevSecOps практик, подходы к написанию качественного кода, вопросы регуляторики, ИИ, функциональную безопасность встраиваемого ПО, а также про инструменты композиционного, статического, динамического анализа. Первый вебинар "Методы защиты и обеспечения безопасности ПО" пройдет 12 августа в 15:00 🗓 - Александра Уварова (Developer Advocate, PVS-Studio) в своем докладе "Автоматизация процессов обеспечения безопасности" расскажет, как одновременно с ускорением разработки и поставки программного обеспечения должна развиваться безопасность. В рамках вебинара рассмотрим методы эффективной интеграции SAST-инструмента в DevSecOps-пайплайн, чтобы выявлять уязвимости на ранних этапах, снижать риски и повышать эффективность безопасной разработки. - Дьяков Алексей (Эксперт по монетизации и защите ПО, Guardant) в своем докладе "Защита ПО: от анализа угроз до выбора стратегии" расскажет, почему защита ПО — необходимость, а не опция. Рассмотрим модели угроз, способы защиты и оценим, что компания может сделать самостоятельно. Главное — честно сравним, когда выгоднее строить свое решение, а когда покупать готовое. Регистрация доступна по ссылке 🔗 📲Мы в MAX #вебинар

  • Элегантность кода благодаря C++23 Продолжим разбирать приёмы рефакторинга и посмотрим, как std::ranges::views::enumerate помогает сделать код более элегантным. В заметке я рассматривал рефакторинг и оптимизацию кода за счёт объединения циклов. В итоге я остановился на следующем варианте кода: static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {   auto q = sizes.size();   std::vector<int64_t> strides(q);   int64_t acc = 1;   int64_t ne = 1;   for (auto sz : std::ranges::views::reverse(sizes))   {     strides[--q] = acc;     acc *= (sz == 0 ? 1 : sz);     ne *= sz;   }   //.... } Его недостаток в том, что всё равно необходимо использовать переменную q для работы с контейнером strides. Как можно написать ещё лаконичнее, я не сообразил, но такой способ есть! После публикации мне подсказали про enumerate. Эта штука появилась в C++23, и я как-то её пропустил. Сложно уследить за всеми нововведениями C++. С помощью enumerate можно сразу перебирать и элементы, и их индексы: constexpr static auto v = {'A', 'B', 'C', 'D'}; for (auto const [index, letter] : std::views::enumerate(v))     std::cout << '(' << index << ':' << letter << ") "; Будет напечатано: (0:A) (1:B) (2:C) (3:D). Но нам нужен обратный порядок, и такой вариант не подходит: for (auto const [i, sz] :   std::views::enumerate(     std::ranges::views::reverse(sizes))) Этот цикл будет перебирать элементы с конца, а индексы — по возрастанию от 0. Можно сделать, чтобы значения i также шли в обратном порядке? Можно. Это делается с помощью std::views::enumerate(sizes) | std::views::reverse. В итоге можно сократить код ещё на одну строчку: static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {   std::vector<int64_t> strides(sizes.size());   int64_t acc = 1;   int64_t ne = 1;   for (auto const [i, sz] : std::views::enumerate(sizes) | std::views::reverse)   {     strides[i] = acc;     acc *= (sz == 0 ? 1 : sz);     ne *= sz;   }   //.... } Нельзя назвать это большим достижением, но зато на практике применили одну из новых возможностей C++23. Что со скоростью кода? Замер показал, что в рамках погрешности его скорость не изменилось. Т.е. этот вариант работает так же, как вариант №2 — "Мой вариант одним циклом" (см. замер скорости работы). Это не удивительно, так как по сути код идентичен предыдущему.

  • О механизмах запуска PVS-Studio при работе с embedded-проектами на C и C++.

  • 4 авг.40522из kokuykin

    OWASP выпустил новую версию Top 10 for LLM Applications 2026. 1. Prompt Injection 2. Sensitive Information Disclosure 3. Excessive Agency 4. Supply Chain 5. Data and Model Poisoning 6. Unbounded Consumption 7. Misinformation 8. Hidden Context Exposure 9. Vector and Embedding Weaknesses 10. Improper Output Handling Как и другие гайды проекта, этот список формировался силами сообщества. Участники предлагали новые категории и варианты переименования, после чего голосовали за значимость рисков. Подробнее о кандидатах релиза я писал пару месяцев назад. Как можно заметить, изменений немного: все знакомые категории остались в списке. Новые сценарии атак решили распределить между существующими рисками, чтобы не дробить классификацию. Excessive Agency поднялся на третье место, а в приложении появился подробный маппинг этого риска на OWASP Top 10 for Agentic Applications. Unbounded Consumption тоже переместили выше, на шестое место, на фоне распространения reasoning-моделей, длинных контекстов и роста вычислительных затрат. Наконец, убрали противоречивый System Prompt Leakage. Его переименовали в Hidden Context Exposure, расширив риск до раскрытия developer instructions, tool schemas, правил доступа и другой внутренней логики приложения. В гайде есть большой appendix с маппингами на другие фреймворки и интересная диаграмма движения атаки от внешнего контура к последствиям. На входе находятся три основных вектора: Prompt Injection, Supply Chain и Data and Model Poisoning. Дальше атака может усиливаться за счет раскрытия скрытого контекста или избыточных полномочий агента. В центре схемы находятся три основных последствия: утечка чувствительной информации, распространение недостоверной информации и неконтролируемое потребление ресурсов.

  • 4 авг.360122

    Когда ИИ платят за строки кода :) static int ksys_read(int fd, char *buf, u32 len) { u32 i; char c; int pid = (int)ring3_current_pid; int slot; if (fd != 0 || !buf) { if (fd != 0 || !buf) { if (fd == 0) { return ERR_FAULT; } } } .... } Проверка эквивалентна if (!buf && fd == 0) return ERR_FAULT; и выглядит подозрительно. Но мне сейчас лень изучать код дальше. Просто поделился тем, что улыбнуло :)

  • Кто сразу видит ошибку в реализации паттерна double-checked locking в Java коде? :) public class KubernetesDevUIProcessor { static volatile List<Manifest> manifests; public List<Manifest> getManifests() throws BootstrapException { if (manifests == null) { synchronized (Holder.class) { if (manifests == null) { manifests = new ArrayList<>(); .... try (CuratedApplication bootstrap = quarkusBootstrap.bootstrap()) { .... for (var entry : context.entrySet()) { manifests.add( new Manifest(entry.getKey(), new String(entry.getValue())) ); } } } } } return manifests; } } Заглядывайте в публикацию, мы и других багов принесли - Проверяем Quarkus 🔥

  • 27 июл.554из pvsstudio_rus

    В этот чудесный летний день мы предлагаем вам сделать коллаборацию! 🔥 А какие варианты взаимодействия есть и куда писать — можно узнать по ссылке 🔗 #PVS_Studio #статья

  • 24 июл.64972из pvsstudio_rus

    Статический анализатор — мощный, но не всегда простой инструмент. Поэтому мы подготовили для вас курс "Статический анализатор PVS-Studio на практике"! Что вас ждет: - Вы узнаете возможности анализатора, актуальные требования ГОСТ и как устроено лицензирование PVS-Studio. - Познакомитесь с локальной работой с PVS-Studio в плагине для IDE. - Поймёте, как встроить PVS-Studio в рабочий процесс разработки и минимизировать ручную работу. - Узнаете про использование PVS-Studio в роли SAST-инструмента для автоматического поиска ошибок и потенциальных уязвимостей в исходном коде. - Разберётесь с системой мониторинга компиляции и разметкой предупреждений по стандартам MISRA на практике. Подробнее о курсе по этой ссылке 🔗 P.s. курс абсолютно бесплатный 😉

  • Ошибка, которую никто никогда не совершал. Ведь так, да? Уже с первого курса университета — или даже раньше — вы могли догадаться, что писать arr[len(arr)] — плохая затея. И вряд ли так кто-то может ошибиться, правда? Мы тоже так думали, пока не проверили 1000 проектов. Прогрев Go анализатора :)

  • Хорошая новость для разработчиков на Unreal Engine: мы сделали PVS-Studio доступнее, и теперь анализ UE-проектов в PVS-Studio доступен в лицензии Team, а не только в Enterprise. Подробности. Если вы работаете с Unreal Engine и давно присматривались к PVS-Studio, то сейчас самое время попробовать анализатор на своём проекте. А если возникнут вопросы по интеграции или настройке, наша команда поддержки всегда поможет вам разобраться.

  • kstrncmp uint64_t kstrncmp(char *str1, char *str2, uint64_t len){ uint64_t index_str = 0; uint64_t different_char = 0; while (index_str < len) { if (str1[index_str] != str2[index_str]) { different_char++; } index_str++; } return different_char; }

  • Кажется я превратился в тролля. Но автор заслужил :) Мой комментарий к статье "Операционная Система на C без знаний C". Я так понимаю, речь идёт об этом проекте OS. Мдя. Ну что же, автору предстоит узнать ещё много о языке С. Например, что такое выход за границу буфера. void terminal_write_hex(uint64_t hex_num) {     uint64_t index_buffer = 0;     char buffer[sizeof(uint64_t)];     if (hex_num == 0x0) {         terminal_write("0x0");         return;     }     terminal_write("0x");     while (hex_num > 0) {         uint64_t digit = hex_num % 16;         if (digit >= 10) {             digit = digit - 10;             buffer[index_buffer] = 'A'+digit;         }else {             buffer[index_buffer] = '0' + digit;         }         hex_num = hex_num / 16;         index_buffer++;     } Какой милый баг. Посмотрите на размер буфера: не учитывается, что один байт превращается в два символа. Интересные времена "безопасного ПО" настают. Простите за едкость комментария, но не удержался, увидев проект автора: Safe Life System Наш проект — это платформа для обеспечения и сохранения безопасности информации в цифровой среде, а также для развития независимой IT-экосистемы. Мы создаём комплекс программного обеспечения, ориентированный на защиту данных, открытость технологий и поддержку русскоязычного IT-сообщества.

  • видео или голосовое, без подписи

  • видео или голосовое, без подписи

Бестиарий программирования — tgindex