Твои переменные в C++ выглядят как
Пользовательские литералы в C++ (UDL), начиная с C++11, делают такие объявления заметно чище:
- Читаемость:
- Компонуемость:
- Нулевые накладные расходы: вся математика выполняется на этапе компиляции через
- Отсутствие магических чисел:
- Безопасность: можно комбинировать со
👉 @Cpportal
cache_size_in_kb или limit_in_mb?Пользовательские литералы в C++ (UDL), начиная с C++11, делают такие объявления заметно чище:
- Читаемость:
8_mb вместо строки из случайных цифр- Компонуемость:
8_mb + 512_kb- Нулевые накладные расходы: вся математика выполняется на этапе компиляции через
constexpr- Отсутствие магических чисел:
8_mb явно задаёт смысл вместо числа 8388608- Безопасность: можно комбинировать со
static_assert, чтобы ловить несовпадения единиц измерения до сборкиPlease open Telegram to view this post
VIEW IN TELEGRAM
🔥11👀5❤3
Реализовали microGPT от Andrej Karpathy с нуля на чистом языке Си: https://github.com/vixhal-baraiya/microgpt-c
Работает на 2.6 млн токенов/с на процессоре Ryzen 5 5600H.
Оптимизация через AVX2 интринсики, ядра скалярного произведения фиксированной ширины (dot16, dot4, dot64) и быструю аппроксимацию экспоненты Шраудольфа.
👉 @Cpportal
Работает на 2.6 млн токенов/с на процессоре Ryzen 5 5600H.
Оптимизация через AVX2 интринсики, ядра скалярного произведения фиксированной ширины (dot16, dot4, dot64) и быструю аппроксимацию экспоненты Шраудольфа.
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - vixhal-baraiya/microgpt-c: The most atomic way to train and inference a GPT in pure, dependency-free C
The most atomic way to train and inference a GPT in pure, dependency-free C - vixhal-baraiya/microgpt-c
🤯10❤8
Можно создать файл размером 4 ГБ в Linux, который почти не занимает места на диске.
В системе он отображается как файл на 4 ГБ. Можно читать любые смещения — в ответ будут нули. При этом реальные блоки на диске не выделяются, пока в файл не начнут записывать данные.
Размер файла — 4 ГБ.
Фактическое использование дискового пространства — около 0.
Оба значения корректны одновременно.
Это называется разрежённый файл (sparse file).
👉 @Cpportal
В системе он отображается как файл на 4 ГБ. Можно читать любые смещения — в ответ будут нули. При этом реальные блоки на диске не выделяются, пока в файл не начнут записывать данные.
Размер файла — 4 ГБ.
Фактическое использование дискового пространства — около 0.
Оба значения корректны одновременно.
Это называется разрежённый файл (sparse file).
Please open Telegram to view this post
VIEW IN TELEGRAM
❤12👍7🤔3🥱2
В C есть синтаксис, позволяющий передавать структуру напрямую в функцию без предварительного создания временной переменной.
Во многих легаси-кодовых базах на C до сих пор пишут 4–6 строк шаблонного кода только ради однократного вызова одной функции.
👉 @Cpportal
Во многих легаси-кодовых базах на C до сих пор пишут 4–6 строк шаблонного кода только ради однократного вызова одной функции.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥12🥱7❤3👍2
Одно ключевое слово в C сообщает компилятору: эти указатели никогда не пересекаются.
Без него компилятор обязан считать, что любой указатель может алиасить одну и ту же область памяти, и поэтому оптимизировать консервативно.
С ним gcc/clang могут безопасно векторизовать циклы, переупорядочивать загрузки/записи, разворачивать итерации и генерировать SIMD-инструкции.
Это ключевое слово — .
👉 @Cpportal
Без него компилятор обязан считать, что любой указатель может алиасить одну и ту же область памяти, и поэтому оптимизировать консервативно.
С ним gcc/clang могут безопасно векторизовать циклы, переупорядочивать загрузки/записи, разворачивать итерации и генерировать SIMD-инструкции.
restrictPlease open Telegram to view this post
VIEW IN TELEGRAM
❤9👀5🔥4🥱1
Ваш процессор хранит
Само значение не изменилось — изменился порядок байтов. Это называется little-endian.
Большинство современных процессоров (
👉 @Cpportal
0x12345678 в памяти как 78 56 34 12.Само значение не изменилось — изменился порядок байтов. Это называется little-endian.
Большинство современных процессоров (
x86, ARM) работают именно так.Please open Telegram to view this post
VIEW IN TELEGRAM
🥱17❤7👍3👎1👀1
Макрос
Стандартный вариант в C:
вычисляет
В kernel для этого используется GCC-расширение — statement expressions.
👉 @Cpportal
min() в Linux kernel — один из самых тщательно спроектированных макросов вообще.Стандартный вариант в C:
#define min(a,b) ((a)<(b)?(a):(b))
вычисляет
a или b ДВАЖДЫ. Если передать min(x++, y), получите двойной инкремент.В kernel для этого используется GCC-расширение — statement expressions.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔19🔥6❤4👍2😁1
Однажды компилятор C будто бы опроверг Великую теорему Ферма.
Теорема Ферма утверждает, что для целых чисел невозможно:
a³ + b³ = c³
Её доказал Эндрю Уайлс в 1995 году спустя 358 лет после формулировки.
Но позже Джон Рейгер показал цикл, скомпилированный через
“Fermat's Last Theorem has been disproved.”
Проблема была не в математике, а в undefined behavior.
Вот что на самом деле увидел gcc.
В цикле нет I/O и нет
Поэтому gcc может считать, что
Переполнение
С этим допущением он может трансформировать программу так, что логика поиска полностью ломается.
В результате gcc способен удалить или изменить части вычислений так, будто программа всегда находит решение.
Компилятор не ошибся в математике. Он просто следовал стандарту.
Ошибка была в коде, который полагался на undefined behavior.
Потом Рейгер решил проверить компилятор. Он добавил
После этого gcc снова начал выполнять реальные вычисления, потому что теперь у программы появился наблюдаемый вывод.
Часть агрессивных оптимизаций исчезла.
Блеф перестал работать.
Позже Рейгер написал:
> “I got the feeling these tools like Fermat himself had not enough room in the margin to explain their reasoning.”
И проблема решается не простым добавлением
Настоящее решение — не допускать undefined behavior, например переполнения
👉 @Cpportal
Теорема Ферма утверждает, что для целых чисел невозможно:
a³ + b³ = c³
Её доказал Эндрю Уайлс в 1995 году спустя 358 лет после формулировки.
Но позже Джон Рейгер показал цикл, скомпилированный через
gcc -O2, который выводил:“Fermat's Last Theorem has been disproved.”
Проблема была не в математике, а в undefined behavior.
Вот что на самом деле увидел gcc.
В цикле нет I/O и нет
volatile. Стандарт C11 разрешает компилятору предполагать, что такой цикл может завершиться.Поэтому gcc может считать, что
while(1) не обязательно бесконечный. Но проблема была глубже — в undefined behavior.Переполнение
signed int в aaa — это UB. Компилятор исходит из того, что такого переполнения никогда не произойдёт.С этим допущением он может трансформировать программу так, что логика поиска полностью ломается.
В результате gcc способен удалить или изменить части вычислений так, будто программа всегда находит решение.
Компилятор не ошибся в математике. Он просто следовал стандарту.
Ошибка была в коде, который полагался на undefined behavior.
// что gcc увидел при -O2:
// в цикле нет volatile, нет I/O, нет атомарных операций
// стандарт C11 разрешает считать, что цикл может завершиться
// следовательно: если fermat() завершится, то вернёт только 1
// следовательно: можно сразу вернуть 1
// что примерно сгенерировал компилятор:
int fermat() { return 1; } // не гарантируется, но допустимо при UB
Потом Рейгер решил проверить компилятор. Он добавил
printf в конец программы, чтобы вывести найденный контрпример: a b c.После этого gcc снова начал выполнять реальные вычисления, потому что теперь у программы появился наблюдаемый вывод.
Часть агрессивных оптимизаций исчезла.
Блеф перестал работать.
Позже Рейгер написал:
> “I got the feeling these tools like Fermat himself had not enough room in the margin to explain their reasoning.”
И проблема решается не простым добавлением
volatile.Настоящее решение — не допускать undefined behavior, например переполнения
signed int.volatile лишь запрещает часть оптимизаций, но не исправляет саму ошибку.// Приём 1: добавление printf заставляет компилятор выполнить вычисления
printf("%d %d %d\n", a, b, c); // теперь компилятор должен их вычислить
// Приём 2: volatile ограничивает оптимизацию
volatile int a = 1, b = 1, c = 1; // чтение теперь — наблюдаемый побочный эффект
// Этот же баг часто встречается во встраиваемой прошивке
// Цикл ожидания без volatile удаляется при -O2
// Процессор пропускает ожидание, что приводит к нерабочему поведению
Please open Telegram to view this post
VIEW IN TELEGRAM
👍18❤11🌚3
Одна C-функция положила 10% всего интернета в 1988 году.
функция gets() считывает пользовательский ввод в буфер без ограничения по размеру. что бы ни ввёл пользователь — всё записывается. выходит за пределы буфера, дальше в стек, вплоть до адреса возврата.
аспирант по имени Robert Morris использовал это для переполнения fingerd и инъекции шеллкода. червь разошёлся по 6000 машин за несколько часов.
👉 @Cpportal
функция gets() считывает пользовательский ввод в буфер без ограничения по размеру. что бы ни ввёл пользователь — всё записывается. выходит за пределы буфера, дальше в стек, вплоть до адреса возврата.
аспирант по имени Robert Morris использовал это для переполнения fingerd и инъекции шеллкода. червь разошёлся по 6000 машин за несколько часов.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤯17👍6❤3👀2
gcc позволяет запускать код ещё до старта main().
так shared-библиотеки инициализируют себя при вызове dlopen()
числовой приоритет — это то, что делает механизм реально управляемым.
меньшее число выполняется раньше, поэтому если две библиотеки регистрируют конструкторы, функция с приоритетом 101 отработает до 102.
glibc резервирует всё ниже 100 под себя
туда лучше не лезть
деструкторы работают в обратном порядке: большее число выполняется раньше при завершении, симметрия сделана специально
суть работы
ты подгружаешь
из-за этого можно перехватывать
👉 @Cpportal
attribute((constructor)) помечает функцию, которая выполняется на этапе загрузкиattribute((destructor)) выполняется после возврата из main()так shared-библиотеки инициализируют себя при вызове dlopen()
__attribute__((constructor)) void before_main() {
printf("this runs before main\n");
}
int main() {
printf("this runs second\n");
return 0;
}числовой приоритет — это то, что делает механизм реально управляемым.
меньшее число выполняется раньше, поэтому если две библиотеки регистрируют конструкторы, функция с приоритетом 101 отработает до 102.
glibc резервирует всё ниже 100 под себя
туда лучше не лезть
деструкторы работают в обратном порядке: большее число выполняется раньше при завершении, симметрия сделана специально
__attribute__((constructor(101))) void first() {
printf("runs first\n");
}
__attribute__((constructor(102))) void second() {
printf("runs second\n");
}
int main() {
printf("runs last\n");
return 0;
}суть работы
LD_PRELOAD как раз в этом механизме загрузки разделяемых библиотек.ты подгружаешь
.so в процесс, её конструкторы выполняются до входа в main(), и к моменту старта пользовательского кода таблица символов уже переопределена.из-за этого можно перехватывать
malloc, open, read и любые функции из libc, не модифицируя сам бинарник.Please open Telegram to view this post
VIEW IN TELEGRAM
❤21👍9🔥3👀1
Кто-то спросил у Биджа, как работают сокеты в C. Ему надоело объяснять это снова и снова. Поэтому в 1995 году он просто выложил всё в интернет.
С тех пор это главное руководство по сетевому программированию на сокетах уже 30 лет.
Там есть всё: TCP, UDP, IPv4, IPv6, неблокирующий ввод-вывод, select(), poll().
Курсы по операционным системам по всему миру включают его в программу. И оно смешнее, чем техническая книга вообще имеет право быть.
Оно бесплатное. И таким останется всегда.
Базовый цикл работы сокетов не менялся с 1995 года. Каждый сервер, к которому ты когда-либо подключался, работает на какой-то версии этой схемы.
Beej буквально показывает, что происходит внутри connect(), accept(), send(), recv().
Как только понимаешь это на C — понимаешь сокеты в любом языке навсегда.
Зацени. Оно бесплатное и таким останется всегда.
http://beej.us/guide/bgnet
👉 @Cpportal
С тех пор это главное руководство по сетевому программированию на сокетах уже 30 лет.
Там есть всё: TCP, UDP, IPv4, IPv6, неблокирующий ввод-вывод, select(), poll().
Курсы по операционным системам по всему миру включают его в программу. И оно смешнее, чем техническая книга вообще имеет право быть.
Оно бесплатное. И таким останется всегда.
Базовый цикл работы сокетов не менялся с 1995 года. Каждый сервер, к которому ты когда-либо подключался, работает на какой-то версии этой схемы.
Beej буквально показывает, что происходит внутри connect(), accept(), send(), recv().
Как только понимаешь это на C — понимаешь сокеты в любом языке навсегда.
// минимальный TCP-сервер по гайду Beej
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(8080),
.sin_addr.s_addr = INADDR_ANY
};
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 10);
int client = accept(sockfd, NULL, NULL);
send(client, "hello\n", 6, 0);
// nginx, redis и большинство серверов начинают с этого паттерна
Зацени. Оно бесплатное и таким останется всегда.
http://beej.us/guide/bgnet
Please open Telegram to view this post
VIEW IN TELEGRAM
❤21🔥4
Есть вещь, которая каждый раз цепляет, когда об этом задумываешься: целое число в Python занимает 28 байт памяти. В C
Это не баг Python. Это цена динамической типизации. Каждый объект в Python хранит счётчик ссылок, указатель на тип и метаданные размера.
Миллион целых чисел в Python — это около 28 МБ памяти.
Те же данные в C — около 4 МБ.
👉 @Cpportal
int занимает 4.Это не баг Python. Это цена динамической типизации. Каждый объект в Python хранит счётчик ссылок, указатель на тип и метаданные размера.
Миллион целых чисел в Python — это около 28 МБ памяти.
Те же данные в C — около 4 МБ.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤19🏆3👍2
printf сам по себе он ничего не выводитprintf — это по сути всего три строки, и на этом вся функция заканчивается
/* glibc stdio-common/printf.c — вся функция принтф */
int __printf(const char* format, ...) {
va_list arg;
int done;
va_start(arg, format);
done = vfprintf(stdout, format, arg); // вся реальная работа здесь
va_end(arg);
return done;
}
/*
printf — это обёртка, а всю работу делает vfprintf
внутренняя реализация vfprintf в glibc — больше 2400 строк
для функции, которую ты вызываешь в своей первой программе
*/
vfprintf — это место, где реально разбирается форматная строка, это конечный автомат
он проходит по форматной строке побайтно, ищет символ процента, когда находит — читает флаги, ширину, точность, модификатор длины и спецификатор преобразования, после чего прыгает в соответствующий обработчик
printf("%05.2f", 3.14) — это просто цикл с большим оператором switch, и он не пишет напрямую в терминал, а записывает в буфер, который принадлежит stdout
этот буфер сбрасывается в ОС через один системный вызов write, но только когда он заполняется, либо когда печатается перевод строки, либо при завершении программы
/* упрощено из внутреннего парсера vfprintf в C, основной цикл разбора */
while (*f != '\0') {
if (*f != '%') {
/* копирование литеральных символов в буфер */
f = strchrnul(f, '%'); // использует векторные инструкции (SIMD) для поиска следующего '%' или конца
} else {
/* разбор флагов, ширины, точности, модификаторов длины и преобразования */
switch (conv) {
case 'd': /* формат: знаковое целое */ break;
case 's': /* формат: строка */ break;
case 'f': /* формат: число с плавающей точкой */ break;
/* больше вариантов */
}
}
}
/* strchrnul использует векторные инструкции для сканирования нескольких байтов за раз */
/* даже сканирование форматной строки оптимизировано */
// поэтому printf без переноса строки иногда ничего не выводит
stdout использует построчную буферизацию при подключении к терминалу, поэтому отсутствие '\n' не вызывает сброс буфера и вывода
но если перенаправить stdout в файл, он становится полностью буферизованным, и ничего не выводится до завершения программы или вызова fflush
это десятилетиями сбивало с толку разработчиков, и причина всегда в режиме буферизации
printf("hello"); // может не появиться сразу
printf("hello\n"); // вызывает сброс и выводится сразу
fflush(stdout); // или явный сбросфункция, которую вызывают на первом занятии по C, запускает тысячи строк кода glibc и использует векторные инструкции до того, как хотя бы один байт попадёт в терминал
Please open Telegram to view this post
VIEW IN TELEGRAM
❤20🔥13🤯3🌚2
В C++ деструкторы появились ещё в 1985 году, а разработчики на C десятилетиями писали
GCC добавил похожий механизм для C ещё в 2003 году.
Этим уже пользуются такие проекты, как libvirt и QEMU, а Linux kernel принял этот подход в 2023 году.
И всё это уже много лет поддерживается как в GCC, так и в Clang.
В libvirt на базе этого был построен полноценный RAII-подобный механизм на C, где с помощью макроса заменили большинство шаблонов очистки через
Приоритет конструкторов имеет значение в экосистеме Linux, поскольку glibc резервирует приоритеты от 0 до 100, поэтому пользовательские конструкторы должны использовать приоритет 101 и выше.
👉 @Cpportal
goto cleanup и хотели получить что-то вроде RAII.GCC добавил похожий механизм для C ещё в 2003 году.
__attribute__((cleanup)) вызывает указанную функцию в момент, когда переменная выходит из области видимости — в любом scope и при любом пути выхода из функции, включая ранние return.Этим уже пользуются такие проекты, как libvirt и QEMU, а Linux kernel принял этот подход в 2023 году.
И всё это уже много лет поддерживается как в GCC, так и в Clang.
static void freep(void **p) { if (*p) { free(*p); *p = NULL; } }
static void fclosep(FILE **f) { if (*f) { fclose(*f); *f = NULL; } }
int process_file(const char *path) {
__attribute__((cleanup(fclosep))) FILE *f = fopen(path, "r");
__attribute__((cleanup(freep))) char *buf = malloc(4096);
if (!f || !buf) return -1; // both freed automatically
fgets(buf, 4096, f);
printf("%s", buf);
return 0; // fclose and free run automatically here
}В libvirt на базе этого был построен полноценный RAII-подобный механизм на C, где с помощью макроса заменили большинство шаблонов очистки через
goto cleanup.Приоритет конструкторов имеет значение в экосистеме Linux, поскольку glibc резервирует приоритеты от 0 до 100, поэтому пользовательские конструкторы должны использовать приоритет 101 и выше.
/* cleanup.h из Linux kernel: показывает, как ядро использует это начиная с v6.2 */
#define DEFINE_FREE(_name, _type, _free) \
static inline void __free_##_name(void *p) \
{ _type _T = *(_type *)p; _free; } /* достаём значение и вызываем функцию освобождения */
#define __free(_name) __attribute__((__cleanup__(__free_##_name)))
DEFINE_FREE(kfree, void *, kfree(_T)) /* автоосвобождение памяти через kfree */
DEFINE_FREE(fput, struct file *, fput(_T)) /* автоосвобождение ссылки на file */
/* пример использования в реальном коде ядра */
struct file *f __free(fput) = fget(fd);
/* когда переменная f выходит из области видимости,
fput(f) вызывается автоматически */
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9🔥2🤔1👀1
«Введение в реализацию стека протоколов TCP/IP с нуля» объясняет реализацию на языке C, но автор заново реализовал тот же материал на Rust: https://github.com/pandax381/microps-rs
Он реализовал всё в 30 шагов, как и в версии на C, стараясь максимально точно следовать структуре книги.
Обязательно используйте это как дополнительный материал🫡 🫡
👉 @Cpportal
Он реализовал всё в 30 шагов, как и в версии на C, стараясь максимально точно следовать структуре книги.
Обязательно используйте это как дополнительный материал
Please open Telegram to view this post
VIEW IN TELEGRAM
GitHub
GitHub - pandax381/microps-rs: Tiny TCP/IP protocol stack written in Rust, reimplemented from microps.
Tiny TCP/IP protocol stack written in Rust, reimplemented from microps. - pandax381/microps-rs
👎7❤5👍3👀2🔥1
Каждый процесс в Linux получает собственное виртуальное адресное пространство.
Две абсолютно разные программы могут одновременно использовать один и тот же адрес памяти, но при этом ссылаться на разные физические области RAM.
👉 @Cpportal
Две абсолютно разные программы могут одновременно использовать один и тот же адрес памяти, но при этом ссылаться на разные физические области RAM.
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍2👀1
Атомарные операции встроены в современный C через заголовок
Компилятор может транслировать эти операции в нативные атомарные инструкции процессора, что позволяет выполнять потокобезопасные (thread-safe) обновления без использования мьютекса (mutex) для простых счётчиков, разделяемых между потоками.
👉 @Cpportal
<stdatomic.h>.Компилятор может транслировать эти операции в нативные атомарные инструкции процессора, что позволяет выполнять потокобезопасные (thread-safe) обновления без использования мьютекса (mutex) для простых счётчиков, разделяемых между потоками.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍10❤5🔥3
Наследование в C можно реализовать с помощью одного простого правила — сделать родительскую
C гарантирует, что указатель на
Это тот же самый паттерн, который используется в системном коде, где структуры (struct) компонуются и доступны через общую базовую структуру.
👉 @Cpportal
struct первым полем.C гарантирует, что указатель на
struct имеет тот же адрес, что и её первый член, поэтому приведение указателей между ними будет работать корректно.Это тот же самый паттерн, который используется в системном коде, где структуры (struct) компонуются и доступны через общую базовую структуру.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍19🥱6❤4
Большинство пользовательских указателей (user-space pointers) в Linux на x86-64 используют только 48 бит адреса.
Некоторые рантаймы временно хранят метаданные в старших битах, а затем очищают (маскируют) их перед повторным разыменованием указателя. Эта техника называется tagging указателей (pointer tagging).
Рантаймы Lisp, сборщики мусора и JavaScript-движки используют её, чтобы привязывать дополнительную информацию к указателям без дополнительных затрат памяти.
👉 @Cpportal
Некоторые рантаймы временно хранят метаданные в старших битах, а затем очищают (маскируют) их перед повторным разыменованием указателя. Эта техника называется tagging указателей (pointer tagging).
Рантаймы Lisp, сборщики мусора и JavaScript-движки используют её, чтобы привязывать дополнительную информацию к указателям без дополнительных затрат памяти.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤9👍2🔥2🤔1🤯1