cpportal | Unsorted

Telegram-канал cpportal - С/С++ Portal | Программирование

16270

Присоединяйтесь к нашему каналу и погрузитесь в мир для C/C++-разработчика Связь: @devmangx РКН: https://clck.ru/3Foc4d

Subscribe to a channel

С/С++ Portal | Программирование

Большинство разработчиков относятся к сетевым сокетам ровно как к файлам.

Подключился
Записал
Закрыл

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

TCP — это как телефонный звонок. Один раз набрал номер, установил выделенную линию, и дальше всё общение идёт по этому каналу, пока не положишь трубку.

UDP — это как крик через комнату. Каждое сообщение само по себе. Можешь кричать всё время одному и тому же человеку, а можешь повернуться и крикнуть кому-то другому.

sendto() позволяет каждый раз выбирать, кому именно ты кричишь.

Вот как отправить UDP-пакет на конкретный IP без какого-либо установления соединения.

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

struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(53)
};
inet_pton(AF_INET, "8.8.8.8", &addr.sin_addr);

// Отправляем "Hello" на 8.8.8.8:53
sendto(sock, "Hello", 5, 0,
(struct sockaddr*)&addr, sizeof(addr));


А теперь профессиональный приём, который обычно взрывает мозг:

sendto() можно использовать и с TCP-сокетами.

С флагом MSG_FASTOPEN Linux упаковывает данные прямо в начальный SYN-пакет рукопожатия.

Ты буквально «кричишь» свой запрос, пока ещё жмёшь руку.

Именно такое безсостоячное поведение лежит в основе UDP-сервисов вроде DNS.

DNS-серверы тебя не «помнят». Они получают твой sendto()-датаграмм, смотрят на обратный адрес, отправляют ответ и тут же забывают, что ты вообще существовал.

Максимальная скорость. Нулевые накладные расходы на соединение.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

🎬 Что это? А это второй выпуск нового интерактивного шоу «АйТир Лист» от МойОфис

«АйТир Лист» – это шоу, в котором эксперты оценивают технологии, компании, фреймворки и ИТ-решения по шкале от 1 до 4. Каждый выпуск — это 14 табличек от модератора, жаркие дискуссии и итоговый рейтинг, который поможет зрителям разобраться в актуальных трендах и сделать собственные выводы.

Во втором выпуске мы оценим фичи и идиомы C++.
Гости выпуска:
Данил Черепанов, архитектор Редакторов МойОфис
Антон Полухин, эксперт-разработчик C++ Техплатформы Городских сервисов Яндекса

🎥 Смотрите наш юбилейный второй выпуск там, где вам удобно:
VK | YouTube | RuTube

Реклама
ООО "НОВЫЕ ОБЛАЧНЫЕ ТЕХНОЛОГИИ"
ИНН: 7703807270
erid: 2W5zFGSziwi

Читать полностью…

С/С++ Portal | Программирование

Модель программиста, архитектура команд и язык Си

Я потратил много времени на изучение архитектуры команд и модели программиста у разных процессоров, в основном у архитектур на базе ARM и RISC-V.

Отдача оказалась колоссальной.

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

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

Модель программиста описывает, как разработчик программ должен видеть и использовать процессор.
Она скрывает детали железа.

Обычно в неё входит следующее.

• Кодирование команд
Архитектура команд и описание того, как команды выглядят на уровне битов: формат, схема кодирования и правила выполнения.

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

• Регистры процессора
С точки зрения программиста процессор это набор регистров.

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

• Модель исключений
У процессора есть линии прерываний. При внешнем событии он может приостановить выполнение текущей программы и перейти к выполнению обработчика прерывания.

После завершения обработчика процессор возвращается к выполнению исходной программы. Как именно происходит этот переход, сохранение и восстановление состояния — всё это описывается в модели исключений.

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

Записи в память могут объединяться, переупорядочиваться, а процессор может получить подтверждение о записи, даже если данные ещё находятся во внутреннем буфере или кеше и обрабатываются не сразу. Все такие особенности описываются в модели памяти.

Когда ты хорошо понимаешь модель программиста, становится очень легко предсказывать, что система сделает в ответ на конкретный код на Си.

Ты начинаешь понимать:

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

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

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Строим микро-CPU прямо в терминале! 🤯

NanoCore - эмулятор 8-битного CPU + ассемблер + TUI-дебаггер

Максимально минималистично: 256 байт памяти и опкоды переменной длины

https://github.com/AfaanBilal/NanoCore

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Самый уродливый, но, возможно, самый важный open-source проект

Если коротко, то чувак сгруппировал все правила case-folding из Unicode 17 и написал около 3000 строк AVX-512 кернелов, чтобы реализовать полностью корректный регистронезависимый поиск подстрок по всему диапазону Unicode (более 1 млн кодпоинтов), работая напрямую с исходными UTF-8 байтами.

Результат:

-» часто до ~50 раз быстрее ICU

-» при этом «менее ошибочный», чем большинство инструментов поиска — от grep на низком уровне до Google Docs, Microsoft Excel и VS Code

Это не очередная оптимизация по краю. Это попытка сделать поиск по Unicode одновременно быстрым и корректным, без упрощений, которые ломают семантику.

Библиотека StringZilla v4.5 уже доступна для:
C99, C++11, Python 3, Rust, Swift, Go и JavaScript.

Есть подробный разбор:

-» устройство алгоритма
-» AVX-512 реализация
-» бенчмарки на 20+ дампах Wikipedia на разных языках
-» быстрый старт

Полный материал:
https://ashvardanian.com/posts/search-utf8

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Назойливая полезность Emacs

Ссылки:

https://gnu.org/software/emacs/
https://github.com/magnars/multiple-cursors.el

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

В последнее время много споров про сборку, упаковку и статическую или динамическую линковку в Linux.

В Linux есть две основные библиотеки: glibc и musl.

Большинство думает, что Linux есть Linux. Но на деле именно C-библиотека (libc) — это линза, через которую приложение вообще видит ядро.

glibc — тяжёлая, но стандартная библиотека Linux, и выбрана по умолчанию не случайно.

Приоритет максимальная производительность, а не размер.

Куча вручную оптимизированных реализаций для отдельных операций.

Поддержка всего возможного легаси, о котором ты, скорее всего, даже не слышал.

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

musl — лёгкая библиотека, которую часто используют в контейнерах, например в Alpine.

Приоритет минимализм, чистота кода и корректность.

Размер программы важнее максимальной скорости.

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

Попробуй собрать статический бинарь с glibc. Часто начинаются проблемы.. glibc использует dlopen для NSS , например для DNS. Даже если ты собрал «статически», бинарь всё равно может пытаться динамически подгружать внешние либы во время выполнения, просто чтобы разрешить google.com. Это не герметично.

Теперь попробуй musl.
-static. Готово.
Один бинарь, ноль зависимостей, запускается на любом ядре. Святой Грааль деплоя.

Компромисс простой:
- хочешь выжать максимум, оптимизированный под железо, — берёшь glibc.
- Хочешь, чтобы программа запускалась почти везде без возни с зависимостями — выбирай musl.

Думаю, все и так понимают, кто тут победитель.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

а я написал самый быстрый в мире парсер JSON (на C) (быстрее simdjson в отдельном API, по функционалу, конечно уступает simdjson, но доступно для понимания даже школьнику. поставьте лайк, пожалуйста под работой)

https://github.com/default-writer/c-json-parser

Читать полностью…

С/С++ Portal | Программирование

Вокруг fff.nvim снова всплыл типичный запрос:

"сделай бэкенд fff, а UI пусть будет от нашего <plugin>, потому что люди не хотят ставить два плагина"


Автор ответил прямо: нет, интеграции не будет.

Причина простая: слишком много внимания к мелочам, и он хочет собрать свой интерфейс с нуля, чтобы он выглядел и ощущался так, как "должно" для пользователей Neovim

Пример:

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Linux даёт гарантию, на которую почти ни одна другая Unix-подобная ОС не решается. Linux гарантирует, что прямые системные вызовы это публичный API.

Если посмотреть на macOS или OpenBSD, их kernel ABI по сути приватный. Единственный поддерживаемый способ общаться с ядром это через системную библиотеку (libc/libSystem). Если обойти libc и выполнить инструкцию syscall напрямую, они вообще не обещают, что это не сломается после следующего апдейта.

Linux занимает противоположную позицию: write syscall на x86_64 это write, и write останется write навсегда.

Это архитектурное решение скрыто объясняет, почему языки вроде Go ощущаются такими "родными" на Linux по сравнению с другими платформами.

Когда ты компилишь на Linux чистую Go-прогу без cgo, тулчейн не заморачивается линковкой с glibc или musl. Он знает протоколы ядра, поэтому просто генерит чистый ассемблер, чтобы напрямую разговаривать с ОС.

Что в итоге? Получаешь статический бинарь, который запускается на Ubuntu, Fedora, Alpine или на твоём самопальном дистрибутиве, и при этом вообще без userspace-зависимостей.

Попробуй то же на macOS, и Go будет вынужден динамически линковаться с libSystem. Он не может доверять тому, что интерфейс ядра останется стабильным.

Go выучил это на своей шкуре. Годами Go делал raw syscalls на macOS так же, как на Linux. Потом Apple кое-что поменяла, бинарники посыпались, и Go пришлось отступить. Теперь каждый Go-бинарь на macOS линкует libSystem.

С OpenBSD ещё интереснее. Тео де Раадт говорил: "мы тут, в OpenBSD, короли ABI-нестабильности". Они специально меняют номера syscall-ов и их сигнатуры между релизами как меру усиления безопасности. Их совет: "пишите под API, а не под ABI".

Этот раскол есть из-за того, как управляются проекты. macOS и BSD поставляются как цельная ОС (ядро + userland). libc у них под контролем, поэтому они могут менять syscalls под ней как угодно.

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

Отсюда вопрос: модель "libc это API" реально безопаснее для долгосрочного сопровождения системы, или же Linux-модель со "стабильными syscall-ами" это максимальная свобода для разработчиков?

Ниже глянь на интересные GitHub-ссылки из проекта Go:

- Посмотри, какие проблемы всплывают из-за того, что у Go на macOS свои обертки над syscall-ами
- Вышеуказанный вопрос связан со следующим

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Boot-loaders — ключевые обязанности.

> Configuration of the Memory System
Настраивает аппаратный контроллер, который управляет RAM. Выставляет нужное напряжение, частоты и тайминги для физических чипов памяти. Переводит систему от работы на ограниченном внутреннем кэше/СРАМ процессора к использованию основной оперативной памяти (DDR).

> Load the Kernel and Device Tree
Подгружает драйверы, чтобы уметь читать данные с носителя (SD-карта, eMMC, NAND Flash и т. д.). Копирует бинарник ОС (например, zImage) из памяти хранения в заранее определенный адрес в основной RAM. Копирует .dtb (описание железа) в другой, не пересекающийся участок RAM.

> Optionally Load a Ramdisk
Проверяет, указан ли во входящем скрипте временный файловый образ для старта (initramfs/initrd). Если он нужен, копирует с носителя сжатый образ файловой системы в третий уникальный участок RAM. Фиксирует адрес и размер этого ramdisk, чтобы ядро могло позже его найти.

> Setting the Kernel Command-Line
Формирует строку bootargs с ключевыми параметрами запуска (например, console=ttyS0, root=/dev/mmcblk0p1). Записывает эту строку в загруженный Device Tree (или в специальную структуру тегов в памяти), откуда ядро ожидает её прочитать. Устанавливает счетчик команд CPU на стартовый адрес ядра в RAM, после чего загрузчик завершает работу, а управление переходит ОС.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

У твоего Linux-процесса есть встроенный кухонный таймер. 🍳

sleep() может делать паузы, но он полностью стопорит программу.

Есть неблокирующая альтернатива — getitimer.

Это системный вызов номер 36 на x86_64, который позволяет твоему коду продолжать работу, пока таймер тикает в фоне.

Представь, что sleep() это когда ты стоишь у микроволновки и ждешь, пока на табло не появится 00:00. Делать что-то параллельно нельзя.

Интервальные таймеры это как поставить время на микроволновке и уйти резать овощи.

Код продолжает выполняться, а ядро присылает тебе сигнал (SIGALRM), только когда таймер срабатывает.

Но что, если тебе нужно просто проверить, сколько времени осталось, не останавливаясь?

Вот тут и нужен getitimer.

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

Это важно для:

- watchdog-механизмов (детект подвисших задач)
- профилировщиков (учёт CPU time)
- планировщиков задач

Вот пример на C.

Мы ставим таймер на 1 секунду (через setitimer), имитируем какую-то работу, а затем вызываем getitimer, чтобы посмотреть, сколько времени осталось.

#include <sys/time.h>
#include <stdio.h>
#include <unistd.h>

int main() {
// Set timer for 1 second
struct itimerval timer = {0};
timer.it_value.tv_sec = 1;
setitimer(ITIMER_REAL, &timer, NULL);

// Simulate work (500ms)
usleep(500000);

// Peek at the kitchen timer!
struct itimerval curr;
getitimer(ITIMER_REAL, &curr);

long total_us = curr.it_value.tv_sec * 1000000
+ curr.it_value.tv_usec;
printf("Time left: %ld us\n", total_us);
return 0;
}


Примечание: хотя getitimer классическая Unix-фича, в современных приложениях чаще используют более новую POSIX-API для таймеров (timer_gettime).

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Оператор стрелка (->) это просто шорткат, синтаксический сахар, чтобы код выглядел чище и читался легче.

— (*p).x: перейти по адресу, на который указывает p, получить всю структуру целиком и взять у неё поле x.
— p->x: перейти по адресу, на который указывает p, и сразу обратиться к полю x.

В памяти происходит одно и то же, но -> избавляет от постоянных скобок.

Технические детали (приоритет операторов)

Чтобы понять, зачем вообще появился ->, нужно вспомнить, как C парсит выражения.

— Оператор точки (.): доступ к полям обычной структурной переменной, например myStruct.x.
— Оператор разыменования (*): получить значение по адресу, хранящемуся в указателе.

В C оператор точка имеет более высокий приоритет, чем оператор *.

Если написать *p.x, компилятор прочитает это как *(p.x). Это не работает, потому что p это указатель, у него нет поля x, к которому можно обратиться первым.

Чтобы всё скомпилилось, нужно заставить компилятор сначала разыменовать указатель: (*p). После этого у нас уже структура, и мы можем использовать точку: (*p).x.

Но писать (*p).x каждый раз неудобно, поэтому в языке появился оператор ->, который объединяет разыменование и доступ к полю в одно действие.

Если смотреть на диаграмму памяти:

Указатель (p): это как листок с записанным адресом на длинную цветную полоску в памяти.
*p: это «вид» всей структуры целиком (красная, зелёная и синяя части).
(*p).x: ты смотришь на всю структуру и затем фокусируешься на красной части — поле x.
p->x: используешь указатель, чтобы «стрелой» попасть прямо в поле x, пропустив мысленный шаг с получением всей структуры.

Вот и всё. Стрелка просто делает запись короче, а смысл остаётся тем же.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Ты раньше мог за 10 секунд собрать нативное десктопное приложение. Без Electron, без игровых движков, без веб-фреймворков. Просто лёгкий и быстрый EXE, который компилировался за пару секунд. Работал на любом Windows-компе без интернета. Ощущение, что разработка ПО реально идёт вспять.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Взгляды Линуса на ИИ:

«ИИ — это очевидный пузырь, но он изменит то, как выполняется большинство квалифицированных работ».

«Vibe-кодинг отлично подходит, чтобы войти в программирование, но для поддержки это ужасная вещь».

«Я большой сторонник ИИ. Но я совсем не сторонник всего, что крутится вокруг ИИ. Рынок и маркетинг вокруг него кажутся мне нездоровыми. Обвал будет».

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

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

[https://regexone.com]

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Эмулятор PlayStation 4 для Windows, Linux и macOS, написанный на C++.

Уже запускает Bloodborne, Red Dead Redemption, Yakuza 0 и другие игры.

100% opensource

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Мы постоянно используем sudo, чтобы запускать команды от root. Но если смотреть с точки зрения системы, обычный процесс не может просто взять и решить стать администратором.

Вот как ядро Linux и бинарник sudo работают вместе, чтобы это стало возможным.

Если посмотреть на файл sudo на диске, можно заметить странность в правах доступа.

Там, где у владельца обычно стоит x (execute), ты увидишь s. Это setuid bit (set user ID).

Когда у файла установлен этот бит, ядро Linux запускает программу не с правами пользователя, который её вызвал. Вместо этого эффективный UID процесса устанавливается равным UID владельца файла.

А так как sudo принадлежит root, то при выполнении бинарника через execve() процесс сразу стартует с эффективным UID root в момент, когда ты нажал Enter.

Несмотря на то что процесс sudo уже имеет root-права, он не выполняет твою команду сразу. Сначала он работает как привратник и делает несколько проверок:

1. Аутентификация: проверяет, кто ты такой (проверка пароля через PAM, Pluggable Authentication Modules).

2. Авторизация: смотрит в файл /etc/sudoers (или LDAP), разрешено ли твоему пользователю выполнять именно эту команду на этом хосте.

3. Логирование: записывает попытку в логи (и при настройке может даже логировать ввод-вывод).

Когда sudo убеждается, что ты авторизован, ему нужно запустить команду, например apt update.

Согласно man-странице, у sudo есть две модели процессов:

1. По умолчанию: sudo выделяет псевдотерминал (pty), форкает процесс-монитор, который затем форкается ещё раз и вызывает execve() для запуска команды. Монитор обрабатывает сигналы управления заданиями между твоим терминалом и запущенной командой.

2. Без pty: sudo вызывает fork(), настраивает окружение выполнения и использует execve() для запуска команды в дочернем процессе. Основной процесс sudo ждёт завершения.

В обоих случаях дочерний процесс наследует root-права.

Часто путают sudo и su (substitute user). Ключевые отличия такие:

» su: требует пароль целевого пользователя (обычно root). Ты получаешь интерактивную оболочку этого пользователя, пока не выйдешь.

» sudo: использует твой собственный пароль. По умолчанию учётные данные кэшируются на уровне терминала на 5 минут, поэтому следующие команды sudo не требуют повторной аутентификации.

Это позволяет администраторам выдавать конкретным пользователям повышенные привилегии для конкретных задач, не раскрывая пароль root.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

C++: размещение в памяти и директивы компилятора

Сначала посмотри сюда: https://godbolt.org/z/ox1aWEr16

Визуализация показывает, как три директивы компилятора C++ сильно меняют размер и организацию простого struct, управляя выравниванием и паддингом в памяти.

Разница между стандартным (8 байт) и упакованным (5 байт) layout критична и для производительности, и для передачи данных.

Варианты layout:

-» Default: естественное выравнивание. После char добавляется 3 байта паддинга, чтобы выровнять int по 4-байтной границе.
Размер: 8 байт.

-» Packed: паддинг не добавляется. int начинается сразу после char, но доступ к невыровненным полям может требовать нескольких инструкций CPU, из-за чего экономия памяти может не дать выигрыша.
Размер: 5 байт.

-» Aligned: заданное выравнивание (например, aligned(16) или aligned(32)) может добавить большой хвостовой паддинг, чтобы следующий struct начинался с нужной границы.

Почему layout от компилятора важен:

-» Сетевые протоколы: packed-структуры используют как минимальный wire format, чтобы не гонять по сети лишние байты паддинга.

-» Аппаратные интерфейсы: конкретные выравнивания нужны для memory-mapped I/O, когда железо ожидает данные по определённым адресным границам.

-» Оптимизация производительности: выровненный доступ нужен для векторизации (SIMD) и более быстрого обработки данных.

-» Кросс-платформенность: гарантирует одинаковый layout памяти при компиляции под разные архитектуры.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Отдать статический файл на Linux кажется простой задачей.

- read() с диска
- write() в сеть

Но для высоконагруженных серверов такой подход — убийца производительности.

Почему? Из-за проблемы «посредника».

Вот как системный вызов sendfile (№40 на x86_64) решает это с помощью zero-copy.

В классической схеме read + write данные делают дорогой крюк:

Диск -» буфер ядра -» пользовательское приложение -» буфер ядра -» сетевая карта

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

sendfile вырезает посредника, то есть тебя. 😎

Он говорит ядру:

«Перемести X байт из этого файлового дескриптора напрямую в этот сокет».


Данные вообще не попадают в память приложения. Они остаются в пространстве ядра и идут из страниц кэша напрямую в сетевой стек.

Минимальный пример на C, который копирует данные из одного файлового дескриптора в другой.

Обрати внимание: мы не выделяем буфер под read(). Мы просто связываем дескрипторы.

#include <sys/sendfile.h>
#include <fcntl.h>
#include <stddef.h>

int main() {
int in = open("source.txt", O_RDONLY);

// Отправляем 4096 байт из файла 'in' в stdout (1)
sendfile(1, in, NULL, 4096);
}


Примечание: stdout должен быть файлом или пайпом, а не TTY. Запускать так:
./program > out.txt


Этот приём называется zero-copy.

Значит ли это буквально «ноль копирований»?
С точки зрения CPU относительно userspace — да.

На уровне железа, если сетевая карта поддерживает scatter-gather, она может делать DMA прямо из страница кэша, вообще обходя CPU.

Забавный факт:

В Linux 2.6.x sendfile был ограничен — приёмником обязательно должен был быть сокет (оптимизация под веб-серверы).

Начиная с Linux 2.6.33 вернули старую возможность - приёмником может быть любой файл. То есть sendfile теперь можно использовать и для быстрого копирования локальных файлов.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Rust vs C++

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Наткнулся на чувака, который тащит свой многопоточный рантайм на C прямо в веб.

Пока ранняя версия, но уже работает полный многопоточный рендер + физика на WebGPU и WASM: 100k кубов и стабильные 60 FPS. Скоро обещает сравнить по бенчам с ECS от Unity. 🍿

У WASM нет нормального тредингового рантайма, так что пришлось писать его с нуля. Это значит: стеки потоков, локальное хранилище потока, мьютексы, семафоры и т.д. Всё в юзерспейсе, спрятано за слоем абстракции os_.

Схема примерно такая:

- os_thread_launch: поднимает состояние потока, выделяет память под стек и локальное хранилище
- js_thread_spawn: поднимает WebWorker (использует пул, чтобы меньше упираться в блокировки)
- worker.ts: настраивает указатели на стек и локальное хранилище и запускает коллбек

Кайф, когда все ядра реально загружены.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Быстрый инсайт по Linux:

Не обязательно писать циклы, которые постоянно проверяют, изменился ли файл.

Это называется polling. Он жжет CPU просто чтобы бесконечно спрашивать: "Ну что, уже?".

В Linux есть умнее подход: можно сделать так, чтобы ядро само сообщало, когда что-то произошло.

Это inotify.

Думай про inotify как про подписку на события файловой системы.

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

Когда файл меняют, удаляют или к нему обращаются, ядро будит твою программу и отдает событие.

Так эффективно работают всякие "live reload" веб-сервера.

Они не читают диск по кругу. Они спят и ждут, пока ядро их не пнет.

В C это пишется просто. Нужно всего три шага:

1.inotify_init() - просишь ядро создать новый inotify-инстанс. В ответ получаешь file descriptor.

2. inotify_add_watch() - передаешь папку, которую хочешь мониторить (например, /tmp), и события, которые тебя интересуют (типа IN_MODIFY или IN_CREATE).

3. read() - самое приятное: читаешь события обычным системным вызовом read() с этого file descriptor.

На этой строке программа просто встанет (block) и будет ждать, пока реально не прилетит событие. Пока ждешь, CPU не расходуется.

На 2 демо простой C-пример, который следит за директорией и печатает каждый раз, когда файл там "трогают".

Отличный шаблон, если хочешь собрать свой file-watcher.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Вполне валидные выражения в C++:

    [](){};
[](){}();;
[]{};
[]{}();


😁😁😁

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Нашел на GitHub годный список по низколатентной разработке

для C-разработчиков, работающих с нагрузкой:

• как правильно мерить задержки,
• как избегать лишнего движения данных,
• как снижать контекстные переключения,
• почему ждать — дорого,
• паттерны безлоковой разработки,
• работа с памятью и layout структур,
• тонкости кэшей, NUMA и прочего железа.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Нашли мощный ресурс для разрабов, которые поглядывают в сторону квантов и HFT

На GitHub подняли подробный гайд по входу в квантовую разработку и алготрейдинг.

Системное программирование, low-latency архитектуры, работа с памятью, сетевой стэк, многопоточность, плюс темы по алготрейдингу, которые реально спрашивают на собесах в хедж-фонды и высокочастотные конторы.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

В юзерспейсе UNIX/Linux есть важный слой — libc, стандартная C-библиотека.

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

Много функций, которые по привычке считают «системными вызовами», на самом деле обычные врапперы в юзерспейсе.

Хороший пример это как раз семейство exec. В Linux существует только один реальный системный вызов execve(), а libc поверх него даёт execl(), execlp(), execv(), execvp(), execle() и так далее.

Все они внутри вызывают execve(). Когда ты пишешь execl("/bin/ls", "ls", NULL), libc берёт глобальную переменную environ и сама передаёт её в execve(). То есть «автоматическое наследование» среды происходит в юзерспейсе, не в ядре (это к вопросу из недавних обсуждений), хотя ядро дальше само раскладывает стек процесса.

printf() это как раз ещё один показательный пример буферизации на стороне юзерспейса. Попробуй:

printf("Hello");
printf(" World");


stdout может сидеть в буфере libc, обычно в 4 КБ. И только когда буфер заполнится, процесс завершится или ты явно вызовешь fflush(), libc совершит реальный системный вызов write().

strace покажет:

write(1, "Hello World", 11Hello World) = 11


Два вызова printf() -> один write(). Вся буферизация прошла в юзерспейсе.

stdout может работать в построчном режиме (сбрасывает буфер на \n), тогда будет по одному syscalл на строку. Но суть та же — libc делает часть работы до ядра.

sleep(1) и sleep(3) — тоже просто врапперы: собирают структуру timespec и вызывают один и тот же системный вызов. Это легко увидеть через strace.

Архитектура такова: твой код вызывает удобные функции -> libc занимается подготовкой/буферизацией/конвертациями в юзерспейсе -> ядро выполняет привилегированные операции через системные вызовы.

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

Главная мысль, в том что не всё, что внешне похоже на системный вызов, им является. Понимание границы юзерспейс/ядро — базовый навык в системном программировании.

Хочешь покопаться сам: strace показывает системные вызовы, ltrace — вызовы libc. man 2 — про syscalls, man 3 — про функции библиотеки.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

В GitHub появился stb-style проект для динамической триангуляции, написанный на чистом C.

Библиотека ориентирована на геймдев и предназначена для построения navmesh в рантайме. Автор делает упор на минимализм и производительность, стараясь сохранить дух проектов Шона Барретта. Сейчас активно идёт полировка публичного API, релиз уже совсем близко.

Репозиторий: https://github.com/raylee9919/cdt

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

В Microsoft провели внутреннее обучение по реализации переносимой функции sleep на чистом C.

Решение пишется полностью с нуля, без использования libc, а единственной зависимостью выступает само ядро операционной системы. В реализации также используется inline-ассемблер для работы на низком уровне. Такой подход демонстрирует, как можно реализовать базовые системные примитивы без стандартных библиотек и с минимальной абстракцией.

👉 @Cpportal

Читать полностью…

С/С++ Portal | Программирование

Когда ты вводишь в Linux команду ls > file.txt, сама программа ls вообще не знает, что пишет в файл.

Для неё это по-прежнему вывод в терминал.

Эта иллюзия обеспечивается одним конкретным системным вызовом: dup (syscall №32 на x86_64). Ниже разбор по шагам.

Сначала напомним, что каждый процесс в Linux стартует с тремя стандартными открытыми потоками:

0: stdin (ввод)
1: stdout (вывод)
2: stderr (вывод ошибок)

Системный вызов dup(old_fd) создаёт копию существующего файлового дескриптора.

Но он следует одному жёсткому правилу ядра - = всегда возвращает наименьший свободный номер дескриптора.

Чтобы сделать редирект вывода, shell выполняет хитрую перестановку перед запуском команды:

Открывает целевой файл (получает, допустим, fd 3). Закрывает stdout (fd 1). Сразу же вызывает dup(3).

Так как fd 1 теперь самый младший свободный, ядро принудительно подставляет подключение файла именно в fd 1.

Вот пример на C, который полностью имитирует > file.txt.

Обрати внимание: printf по-прежнему пишет в stdout (внутри это просто запись в fd 1), но текст уходит в файл, потому что мы подменили файловые дескрипторы.

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
int fd = open("log.txt", O_WRONLY|O_CREAT, 0644);

close(1); // Закрываем stdout
dup(fd); // fd становится равен 1, потому что 1 свободен

printf("Hello, File!\n");

return 0;
}


Важная техническая деталь:

dup не копирует данные файла. Он создаёт новую ссылку на то же самое открытое файловое описание.

Это означает, что исходный FD и новый FD разделяют один и тот же курсор (offset).

Если ты сместишь указатель через lseek на одном дескрипторе, второй сместится мгновенно. Они связаны.

Хотя dup отлично показывает саму идею, в продакшене чаще используют его родственника dup2(old, new).

dup2 позволяет явно указать, какой номер ты хочешь получить (например, «положи этот файл в fd 1»).

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

Этот механизм лежит в самом сердце Unix-философии.

Программам не нужен сложный код для записи в файлы, пайпы или сокеты. Они просто пишут в fd 1.

А shell ещё до старта процесса решает, куда именно будет указывать fd 1. Это и есть идеальное разделение ответственности.

👉 @Cpportal

Читать полностью…
Subscribe to a channel