С/С++ Portal | Программирование
16.2K subscribers
1.15K photos
217 videos
24 files
835 links
Присоединяйтесь к нашему каналу и погрузитесь в мир для C/C++-разработчика

Связь: @devmangx

РКН: https://clck.ru/3Foc4d
Download Telegram
Что быстрее: Rust или C?

Не нужно гадать, можно просто посмотреть ночные лидерборды проекта Github Primes. Каждую ночь до 100 разных языков программирования прогоняются по бенчмаркам на одном и том же алгоритме поиска простых чисел:

https://plummerssoftwarellc.github.io/PrimeView/report

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
🤣167
Этот код ведет себя по-разному в C и C++

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔147
getrusage - Linux syscall #98

У каждого процесса в Linux есть скрытая "табель успеваемости". Там фиксируется, сколько CPU-времени ты реально сжег, сколько памяти успел отхватить, и как планировщик дергал твой процесс. Все это можно вытащить одним системным вызовом: getrusage (syscall #98 на x86_64).

Структура rusage, которую он заполняет, это та же самая структура, которую использует команда time. Когда ты видишь user, sys и real time, эти значения ядро как раз и прокидывает через struct rusage.

Но там не только про время. Самая мощная штука, которую легко пропустить: переключения контекста.

getrusage делит их на две разные категории:

Voluntary (ru_nvcsw): процесс сам добровольно отдал CPU до того, как закончился его тайм-слайс. Обычно это значит, что он ждет I/O, mutex или какой-то ресурс.

Involuntary (ru_nivcsw): ядро принудительно сняло процесс с CPU. Либо проснулся более приоритетный процесс, либо кончился тайм-слайс.

Это разделение реально дает чит-код для дебага.

Много voluntary переключений? Скорее всего ты уперся в I/O или бодался за локи. Много involuntary? Значит, ты CPU-bound или воюешь за ресурсы на забитом сервере.

Еще там есть ru_maxrss (Maximum Resident Set Size) - пиковое потребление физической RAM процессом, в килобайтах.

Ниже полный пример на C: он немного грузит CPU, а потом печатает свою же "карточку":

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

int main() {
struct rusage usage;
volatile int i = 0;

// Burn some CPU cycles to generate user time
for(i = 0; i < 500000000; i++);

// Get statistics for the calling process
if (getrusage(RUSAGE_SELF, &usage) == 0) {
printf("User CPU time: %ld.%06ld s\n",
usage.ru_utime.tv_sec, usage.ru_utime.tv_usec);
printf("System CPU time: %ld.%06ld s\n",
usage.ru_stime.tv_sec, usage.ru_stime.tv_usec);
printf("Max RAM used: %ld KB\n", usage.ru_maxrss);
printf("Voluntary switches: %ld\n", usage.ru_nvcsw);
printf("Involuntary switches: %ld\n", usage.ru_nivcsw);
}

return 0;
}


В следующий раз, когда приложение "тормозит", не шамань. Спроси у ядра, что реально произошло.

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
13🔥6
Please open Telegram to view this post
VIEW IN TELEGRAM
😁224💊3👍2
Data Structures for Text Sequences, Charles Crowley

Добавляю эту статью в список чтения. Текстовый редактор это один из самых интересных примеров софта, где все крутится вокруг последовательностей текста, и мне особенно любопытен gap buffer.

https://www.cs.unm.edu/~crowley/papers/sds.pdf

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍3
sysinfo - Linux syscalls #99

Парсить текстовые файлы, чтобы проверить состояние системы на Linux, может быть неэффективно. Каждый раз, когда ты делаешь cat /proc/meminfo или читаешь /proc/uptime, ядро вынуждено форматировать внутренние данные в текст, а твоя программа потом парсит этот текст обратно. Есть способ быстрее: sysinfo (syscall #99 на x86_64).

Этот системный вызов возвращает сырой бинарный снимок “жизненных показателей” системы за один context switch:

- Uptime (секунды с момента загрузки)
- Load averages
- Всего и свободно RAM
- Использование swap
- Количество процессов

На самом деле в manpage отмечено, что вся информация, которую дает этот syscall, также доступна через /proc/meminfo и /proc/loadavg. Большинство популярных утилит вроде uptime и top реально читают данные именно из /proc. Но если ты пишешь код, чувствительный к производительности, и тебе нужны эти метрики без оверхеда на открытие файлов и парсинг текста, sysinfo дает все одним вызовом.

Ниже полный, запускаемый пример на C, чтобы собрать свой “мгновенный” системный монитор:

#include <stdio.h>
#include <sys/sysinfo.h>

int main() {
struct sysinfo si;

// Вызов syscall #99
if (sysinfo(&si) != 0) {
perror("sysinfo failed");
return 1;
}

printf("System Vital Signs:\n");
printf("-------------------\n");
printf("Uptime: %ld seconds\n", si.uptime);
printf("Processes: %d running\n", si.procs);

// Считаем память в мегабайтах
// totalram масштабируется через mem_unit (обычно 1 байт, но может быть больше)
unsigned long long total_mem =
(unsigned long long)si.totalram * si.mem_unit;
unsigned long long free_mem =
(unsigned long long)si.freeram * si.mem_unit;

printf("Total RAM: %llu MB\n", total_mem / 1024 / 1024);
printf("Free RAM: %llu MB\n", free_mem / 1024 / 1024);

return 0;
}


👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
1👍9🔥54
В этом месяце hello world сказал: hello world!

Термин ввели в культовой книге по программированию 1978 года The C Programming Language, написанной Брайаном Керниганом и Деннисом Ритчи: https://go.aws/37TmOZq

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
23😁5👍2
times (Linux syscall #100)

Твоя Linux-прога живет в двух параллельных мирах: userspace и kernel space. Когда ты оптимизируешь код, важно понимать, какой мир жрет твои циклы. Для этого и нужен syscall times: #100 на x86_64. Когда ты запускаешь time ./app, видишь вывод Real, User и Sys.

- Real это время по настенным часам (включая ожидание I/O).
- User это время CPU, потраченное на твой код (циклы, математика).
- Sys это время CPU, которое ядро потратило “за тебя” (обработка запросов).

times() это мост к этой статистике по CPU. Он заполняет struct tms счетчиками, измеренными в “тиках” (clock ticks). Чтобы перевести тики в секунды, нужен тикрейт: sysconf(_SC_CLK_TCK), который обычно возвращает 100 на Linux (то есть 100 тиков в секунду).

Есть еще мощная вторичная фича, рассчитанная на shell’ы. В структуре есть tms_cutime и tms_cstime (буква c как child). Когда процесс ждет завершения дочерних процессов (через wait), CPU-стоимость детей складывается в эти поля у родителя. Это накопление рекурсивное: время “внуков” поднимается вверх на каждом уровне wait, но время внуков, которых никто так и не дождался, теряется навсегда.

Так shell может отчитаться по суммарному потреблению ресурсов сложного скрипта или пайплайна, а не только по самому процессу shell.

Вот C-прога, которая профилирует саму себя. Она специально жжет CPU математикой (User time), а потом спамит дешевым syscall’ом (System time), чтобы показать разделение.

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

int main() {
struct tms t;
long clk_tck = sysconf(_SC_CLK_TCK);
volatile int k = 0;

printf("Burning User cycles...\n");
// Heavy math loop (User Time)
for (long i = 0; i < 500000000; i++) {
k += i;
}

printf("Burning System cycles...\n");
// Spamming syscalls (System Time)
for (long i = 0; i < 5000000; i++) {
getpid();
}

// Retrieve the counters
times(&t);

printf("\nTime Analysis:\n");
printf("User CPU: %.2f s\n", (double)t.tms_utime / clk_tck);
printf("System CPU: %.2f s\n", (double)t.tms_stime / clk_tck);

return 0;
}


👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
13🔥6👍5👎1
Современный C++ отлично подходит для написания кастомных DSL.

Я легко могу собрать простой персональный трекер расходов в нескольких строках кода.

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍4
Нашёл познавательную статью от уже знакомого нам автора : Создание 16-битного CPU с нуля на C (пошагово)

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

Что понадобится:

- Базовые знания программирования на C
- Компилятор C (GCC, Clang или MSVC)
- Любопытство и терпение

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

Полный код: https://github.com/vixhal-baraiya/16bit-cpu

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
15
C++ совет дня

Наследование от стандартных типов это отличный способ реализовать аккуратные extension-методы.

Вот небольшой utility для std::string_view, который можно использовать, чтобы декларативно обрезать префиксы и суффиксы.

https://godbolt.org/z/TTxjaMK6o

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
20🤔4💊2
Команда гениев собрала браузер на С++ вообще с нуля.

❤️ Ни строчки кода из Chrome. Ни из Firefox. Ничего не переиспользовано.

Ladybird Browser это:

→ на 100% независимый движок браузера
→ написан на C++ с нуля
→ не форк Chromium, как почти любой "новый" браузер, который ты видел
→ полностью open source, контрибьютить может кто угодно

Chrome сейчас по-тихому крутится под капотом у 95% интернета.

И это open source проект, который пытается это изменить.

59 200+ звезд на GitHub.

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯2610🔥8👎2
getuid: Linux syscall #102

В Linux "кто ты" и "что тебе можно" часто разные вещи. У процесса есть две ключевые идентичности:

- Real UID (RUID): кто запустил процесс (ты)
- Effective UID (EUID): чьи права сейчас действуют (с привилегиями)

Это завязано на syscall getuid (№102 на x86_64).

Зачем нужны два UID?

Возьми команду passwd. Она позволяет обычному пользователю поменять пароль. Но хэши паролей лежат в системном файле, который защищен и писать в него может только root. Как тогда обычный пользователь может записать что-то в файл, принадлежащий root?

У бинарника passwd включен специальный бит Set-UID. Когда ты его запускаешь:

- Effective UID временно становится 0 (root), чтобы дать доступ на запись в файл.
- Real UID остается 1000 (ты).

Дальше программа вызывает getuid(), чтобы проверить Real UID. По нему она понимает, root ты или обычный пользователь, и если ты обычный пользователь, по умолчанию выбирает твой аккаунт и гарантирует, что ты можешь менять только свою запись пароля. Без getuid() Set-UID программа имела бы root-права, но не имела бы способа определить, кто именно ее запустил.

Ниже полный, запускаемый пример на C, который показывает идентичности текущего процесса. Если запустить обычно, оба UID совпадут. Если сменить владельца на root и выставить Set-UID бит (chmod u+s), увидишь, что они расходятся.

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

int main() {
// Syscall 102 (x86_64): getuid (Кто меня запустил?)
uid_t ruid = getuid();

// Syscall 107 (x86_64): geteuid (От чьего имени я действую?)
uid_t euid = geteuid();

printf("Real UID (Identity): %d\n", ruid);
printf("Effective UID (Powers): %d\n", euid);

if (ruid != euid) {
printf("Process is running with borrowed privileges!\n");
} else {
printf("Process is running with standard privileges.\n");
}

return 0;
}


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

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
8👍1
Если тебе нравится перегрузка операторов в C++, то тебе зайдет и перегрузка пробелов! -

уверяет Страуструп 🤭

На самом деле, это первоапрельский прикол от Страуструпа, замаскированный под “предложение для C++2000”. Он сам прямо пишет, что это April Fool’s Joke.

Глянуть: https://www.stroustrup.com/whitespace98.pdf

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
😁246
Не верь байкам.

C++ жив как никогда.

Вот реализация алгоритма решета для простых чисел на современном C++

#include <algorithm>
#include <print>
#include <ranges>
#include <vector>
#include <unordered_set>

// Returns primes in range: 3...2 * n + 2
std::vector<int> sieve_sundaram(int n) {
auto sieve =
std::views::iota(1, n + 1)
| std::ranges::to<std::unordered_set>();

auto sieved_out =
std::views::iota(1, n + 1)
| std::views::transform([n](int j) {
return
std::views::iota(j, n + 1)
| std::views::transform([j](int i) { return i + j + 2 * i * j; })
| std::views::take_while([n](int x) { return x <= n; });
})
| std::views::join;

for (int out : sieved_out)
sieve.erase(out);

auto primes = sieve
| std::views::transform([](int x) { return x * 2 + 1; })
| std::ranges::to<std::vector>();

std::ranges::sort(primes);

return primes;
}

int main() {
std::println("Primes: {}", sieve_sundaram(16));
// Primes: [3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
}


👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥215💊1
Собрал JavaScript-рантайм на C, чтобы реально понять, как устроены Node.js, Bun и Deno

Jade Runtime покрывает:

▪️интеграцию движка JavaScriptCore
▪️архитектуру event loop на libuv
▪️биндинги к нативному API
▪️реализацию асинхронного I/O

Полное руководство с кодом - https://devlogs.xyz/blog/building-a-javaScript-runtime

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍82🔥2👎1
This media is not supported in your browser
VIEW IN TELEGRAM
Большинство людей каждый день пользуются Linux.

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

Вот какая последовательность шагов проходит Linux, прежде чем ты увидишь экран логина:

Нажали power → BIOS или UEFI инициализирует железо и гоняет POST.

Прошивка находит на диске загрузчик (bootloader).

Bootloader грузит в RAM Linux kernel и initramfs.

Ядро распаковывается и забирает управление CPU.

Инициализируются менеджер памяти и планировщик (scheduler).

Поднимаются драйверы устройств, чтобы общаться с железом.

Монтируется временная корневая файловая система из initramfs.

Стартует PID 1 как systemd, начинается userspace.

Системные сервисы и демоны запускаются по порядку.

Становится доступен login prompt или GUI.

Ты запускаешь команду, и она становится процессом с PID.

Процесс работает в user mode с ограниченными правами.

Когда нужен доступ к железу, делается системный вызов (system call).

CPU переключается из user mode в kernel mode.

Ядро валидирует запрос и выполняет его через драйверы.

Результат возвращается обратно в userspace.

Планировщик постоянно раздаёт процессам кванты CPU.

Виртуальная память изолирует и защищает процессы.

Файловая система даёт абстракцию данных поверх хранилища.

Сетевой стек обрабатывает пакеты внутри ядра.

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

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍175
Пример на C, как сымитировать "class"

#include <stdio.h>

typedef struct Counter Counter;

struct Counter {
int a;
int b;
void (*add_and_print)(Counter *);
};

void counter_add_and_print(Counter *c) {
c->a += c->b;
printf("значение = %d (добавили %d)\n", c->a, c->b);
}

int main(void) {
Counter c = { .a = 0, .b = 3, .add_and_print = counter_add_and_print };
c.add_and_print(&c);
c.add_and_print(&c);

return 0;
}


👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
25👎5👍1
Классный инсайт про хеш-функцию djb2 из модуля Hash, который идёт в комплекте с Jai.

djb2_hash :: (s: string) -> u32 {
// Хеш djb2.
// Людям он, похоже, нравится, но для строк ассетов, которые мы часто видим,
// он выглядит довольно последовательным. Например, если у тебя имена ассетов
// "foo1", "foo2", "foo3", они все захешируются в последовательные слоты,
// потому что отличаются только последним символом, а эти символы идут подряд!

hash := HASH_INIT;

for 0..s.count-1 #no_abc #no_aoc {
hash = ((hash << 5) + hash) + s[it]; // hash * 33 + c
}

return hash;
}


👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
8
Люди думают, что C это строгий язык

А C позволяет между делом вплести switch внутрь while, поломать пространство-время, и оно спокойно скомпилится.

Не показывайте это питонистам, они расплачутся. 🥱

#include <stdio.h>

int main() {
int i = 0;

switch (i) {
case 0:
while (i < 3) {
printf("%d", i);
case 1:
i++;
}
}

return 0;
}


👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔28👍95🤣3🔥1
setgid - системный вызов Linux #106

Идентичность процесса в Linux это иллюзия.
Ядро видит потоки.
POSIX ожидает процессы.

Возьмем setgid (syscall #106 на x86_64).

Он выставляет effective group ID (эффективный GID) у вызывающего процесса. Если вызывающий привилегирован (имеет capability CAP_SETGID), он также выставляет real GID (реальный GID) и saved set-group-ID (сохраненный setgid). Чаще всего это видно, когда сервер вроде Nginx сбрасывает привилегии с root на менее привилегированную группу ради безопасности. Обычно ты предполагаешь, что это относится ко всему процессу. Но вот подвох: в Linux ядро ведет учет credentials (учетных данных) на уровне потока.

Если определить "процесс" как группу потоков, которые шарят память, ядро не обязано заставлять их шарить credentials. Если обойти стандартную библиотеку и дернуть сырой syscall в многопоточном приложении, изменится только вызывающий поток. В итоге получишь процесс с раздвоенной идентичностью: один поток уже ограничен, а фоновые worker-потоки продолжают работать со старыми групповыми привилегиями.

Чтобы это исправить, реализация потоков glibc NPTL делает синхронизационный танец. Когда ты вызываешь setgid(), обертка не просто вызывает сырой syscall. Она шлет сигнал каждому другому потоку в процессе. Обработчик сигнала в каждом потоке затем вызывает тот же syscall для самого себя, обновляя свои credentials, чтобы они совпали. glibc ждет, пока все потоки закончат обновление, и только потом возвращает управление. Так она выполняет семантику POSIX, где требуется, чтобы все потоки в процессе имели одинаковые credentials.

Ниже C-программа, которая демонстрирует вызов raw syscall.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <errno.h>

int main() {
// We will try to switch to GID 1000
gid_t target_gid = 1000;

printf("Current GID: %d\n", getgid());

// We use the raw syscall (setgid)
// On a multi-threaded app, this is dangerous!
// It only updates the credential for the CURRENT thread.
printf("Calling raw setgid syscall...\n");
long ret = syscall(SYS_setgid, target_gid);

if (ret == 0) {
printf("Success! New GID: %d\n", getgid());
} else {
perror("Syscall failed (are you root?)");
}

return 0;
}


Это идеальный пример того, как механизм в ядре ОС отличается от спецификации POSIX , и какой “клей” нужен, чтобы все это склеить.

👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
10