При программировании на C с компилятором GCC можно объявлять функции внутри функций.
Насколько известно, это работает только в GCC.
👉 @Cpportal
Насколько известно, это работает только в GCC.
Please open Telegram to view this post
VIEW IN TELEGRAM
👀20🏆5❤2💊1
В Linux есть фича уровня ядра, которая позволяет выполнять код на каждом сетевом пакете. Её используют Nginx и файрволы. Называется BPF.
👉 @Cpportal
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13👀4❤1
goto в C во многих туториалах подаётся как антипаттерн.В ядре Linux kernel он активно используется для обработки ошибок.
Когда требуется освобождать несколько ресурсов, такой подход часто даёт самый простой и корректный контроль потока.
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥21👍7❤3💊1
xor-двусвязный список — одна из тех вещей, которые выглядят слишком умно на первый взгляд
вместо того чтобы хранить
👉 @Cpportal
вместо того чтобы хранить
prev и next, ты хранишь prev ^ next, так что да, экономишь один указатель на узел, но теперь при движении вперёд тебе каждый раз нужен предыдущий узел — напрямую перейти к следующему уже нельзяPlease open Telegram to view this post
VIEW IN TELEGRAM
❤14👀7🤔5
malloc не выделяет физическую память.Он резервирует виртуальное адресное пространство. В Linux (overcommit) ядро возвращает успех даже при нехватке RAM. Реальное выделение происходит при первом доступе к странице (page fault).
Поэтому падения по OOM возникают далеко от места вызова
malloc.Please open Telegram to view this post
VIEW IN TELEGRAM
❤25👍9🤯2🥱2🤔1
int не может одновременно быть 1, 2 и 3 — поэтому такой код не срабатывает.x — одно фиксированное значение, и все сравнения используют один и тот же результат чтения.#include <stdio.h>
int main() {
int x = 1;
if (x == 1 && x == 2 && x == 3) {
printf("all three are true\n");
}
return 0;
}
Но есть вариант, где это возможно.
volatile int *x = (int *)0x40000000;
if (*x == 1 && *x == 2 && *x == 3) {
// possible
}
Здесь нет одного значения — каждое
*x это отдельное чтение из памяти.Если это указатель на memory-mapped регистр, железо может возвращать разное значение при каждом чтении.
В итоге: 1 → 2 → 3 в рамках одного условия.
upd: упрощённая формулировка была неаккуратной (извини). смысл не в том, что одна и та же программа ведёт себя по-разному, а в том, что такой шаблон выражения:
x == 1 && x == 2 && x == 3
невозможен для хранимого значения, но может стать истинным, если каждое чтение идёт из железа (через *x).
вся суть именно в этом различии между источником значения.
Please open Telegram to view this post
VIEW IN TELEGRAM
👎13🔥10❤8🤔4👀1
Собственный текстовый редактор: https://viewsourcecode.org/snaptoken/kilo/
Это примерно 1000 строк C-кода в одном файле без зависимостей. Реализованы все базовые функции минимального редактора, включая подсветку синтаксиса и поиск.
Из этого кода можно сильно прокачаться просто при чтении.
👉 @Cpportal
Это примерно 1000 строк C-кода в одном файле без зависимостей. Реализованы все базовые функции минимального редактора, включая подсветку синтаксиса и поиск.
Из этого кода можно сильно прокачаться просто при чтении.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤16👍3
Указатели на функции — всё, что нужно знать.
Указатели на функции — это указатели, которые ссылаются на код. При разыменовании полученные данные интерпретируются как инструкции, и процессор их выполняет.
При разыменовании указателя на функцию регистр счётчика команд (PC) в процессоре устанавливается в адрес, на который указывает этот указатель.
Полная заметка доступна здесь: https://pyjamacafe.com/posts/function-pointers/
👉 @Cpportal
Указатели на функции — это указатели, которые ссылаются на код. При разыменовании полученные данные интерпретируются как инструкции, и процессор их выполняет.
При разыменовании указателя на функцию регистр счётчика команд (PC) в процессоре устанавливается в адрес, на который указывает этот указатель.
Полная заметка доступна здесь: https://pyjamacafe.com/posts/function-pointers/
Please open Telegram to view this post
VIEW IN TELEGRAM
❤11👍6👀1
Твои переменные в 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