Работа над дневником, запись 05.05.2026

This commit is contained in:
2026-05-05 15:19:20 +05:00
parent 9a0552a496
commit 1f2ebf3bb2
2 changed files with 106 additions and 23 deletions
+66 -2
View File
@@ -757,7 +757,7 @@ l2header* spell_teleport_unchant(void* _RawPacket, SessKey* _Key) {
```C
/**
* @author всё ещё admin@felexdev.ru
* @version +-
* @version ±
*
* @note Алиса, добавь пожалуйста к предыдущим расчётам затраты на клонирование гномиков в БД в процессорном\
* времени, необходимо понять сколько summon-ов нужно для переселения 163961 деревень за секунду
@@ -1121,4 +1121,68 @@ typedef struct ThreadMemoryBlock {
/* Управляющий поток-наблютель */
*(globalPoll.threads[tid].fxalloc) = fxalloc_summary;
*(globalPoll.threads[tid].fxfree) = fxfree_summary;
```
```
# 05.05.2026
Времени вчера оказалось маловато, так и не успели закончить с интерфейсом, на чём мы остановились: использование TLS-переменных-указателей на функции выделения/высвобождения памяти.
## Дополнительные заметки
Из своего опыта разработки(не смотря на то что это было лишь хобби) могу сказать следующее: даже если вы напишите супер-продвинутый модуль, он не будет защищён от некорректного использования, всегда найдутся теб кто решит что он умнее Вас. Как это относится к нашему случаю, да очень просто,— эти переменные могут быть изменены в любом месте вызывающего кода, что повлечёт за собой неопределённое поведение а самое страшное - утечки памяти. Есть ли защита от этого — нет, невозможно полностью защититься, хотя способы минимизировать подобные случаи имеются, пример для `fxalloc`:
### Прокси-функция:
```C
/* includes/FXAlloc.h: публичный интерфейс */
extern thread_local void* (*const fxalloc)(size_t NBytes);
/* headers/_FXAlloc.h: внутренний интерфейс */
static void* fxalloc_proxy(size_t NBytes); ///< функция-прокси
static thread_local void* (*current_alloc)(size_t);///< указатель на действующую функцию алокации
/* src/FXAlloc.c: реализация модуля */
void* (*const fxalloc)(size_t) = fxalloc_proxy;
void* fxalloc_proxy(size_t NBytes) {
/* всё что нужно сделать - вызвать текущую функцию алокации */
return current_alloc(NBytes);
}
```
Что это даёт: практически 100% гарантию что никто и никогда не изменит указатель `fxalloc`, ибо в противном случае будет ошибка компиляции, однако **огромным минусом** данного способа защиты является вызов дополнительной функции, который сразу накидывает ±25 тактов за раз. Это критично для случая работы с памятью и сводит на нет все нашы намерения организовать атомарную синхронизацию так как на весах каждая инструкция процессора.
### Контракт разработчика(CP)
Что подразумевает под собой контракт разработчика(*англ. "Contract Programming": программирование с контрактом*) — код использующий модуль должен соблюдать правила установленные разработчиком этого самого модуля, это одна из основных парадигм системного программирования, да и в целом программирования на Си. Как это влияет на наш случай: мы можем сделать условную компиляцию через макрос без определения которого будут возникать ошибки компиляции так как код внутри модуля будет пытаться изменить константные указатели на функции. Таким образом, для компиляции модуля разработчик использующий этот модуль просто обязан будет физически внести изменения в испоняемый файл(определить макрос) или заголовочный(исправить условную компиляцию), что автоматически снимает с нас ответственность как с разработчиков модуля. Ну а для пущего эфекта назовём макрос длинной фразой с прямым посылом — `_I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_`(Я понимаю что никогда не должен изменять эти указатели) и дополнительно в коментариях к модулю и каждому указателю укажем — **Изменение этих указателей строго запрещено!!!**. Это не гарантирует полной защиты от дурака, зато снимает с нас ответственность: **мы сделали всё что могли** с сохранением скорости вызова нужной функции алокации/высвобождения.
### CP + proxy
Пожалуй, это самый логичный способ защитить модуль от некоректного использования. При компиляции с `const`-указателями работаем через прокси, в противном случае - включаем 3-ю космическую. Пример реализации такого подхода приведён ниже:
```C
/* includes/FXAlloc.h */
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
/**
* @brief Указатель на функцию выделения памяти
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] NBytes: size_t - Количество байт
* @retval `!0` - С адресом кратным размеру `sizeof(size_t)`. Указатель выровненный для любого типа данных
* @retval `NULL` - В случае единственно возможной ошибки `ENOMEM` результат сохранён в `errno`
* подробное описание `strerror(errno)`
*/
extern thread_local void (*const fxalloc)(size_t NBytes);
#else
/**
* @brief Указатель на функцию выделения памяти
* @note Ни в коем разе не должен изменяться из вызывающего кода!!!
* @param[in] NBytes: size_t - Количество байт
* @retval !0 - Кратный размеру(sizeof(size_t)) указатель выровненный для любого типа данных
* @retval NULL - В случае единственно возможной ошибки ENOMEM результат сохранён в errno
*/
extern thread_local void (*fxalloc)(size_t NBytes);
#endif
/* headers/_FXAlloc.c */
#ifndef _I_UNDERSTAND_THAT_I_SHOULD_NEVER_CHANGE_THESE_POINTERS_
void* (*const fxalloc)(size_t) = fxalloc_proxy; ///< работает через `current_alloc`
#else
void* (*fxalloc)(size_t) = fxalloc_init_malloc; ///< работает прямым вызовом
#endif
```
Рабочие функции(вроде `fxalloc_init_alloc`) не знают о существовании дополнительной переменной, им без разницы кто их вызывает, они выполняют свои прямые обязанности несмотря на контекст.