Zpět na domů

Omezení paměti v Lua pískovišti na C++

Článek popisuje implementaci kontroly paměti v Lua pískovišti na C++ prostřednictvím uživatelského alokátoru. Jsou zváženy architektonické vlastnosti, příklady kódu a praktické doporučení pro vývojáře.

Kontrola paměti v Lua: izolace skriptů na C++
Advertisement 728x90

# Omezení paměti a zdrojů v Lua pískovnici na C++

Při integraci Lua do C++ aplikací často vzniká potřeba izolovat provádění skriptů od hlavního systému. To je obzvláště kritické pro scénáře, kde může být cizí kód zlomyslný nebo nestabilní – například v herních enginích, pluginových systémech nebo sandboxových prostředích. Jedním z klíčových aspektů takové izolace je kontrola spotřeby paměti. V tomto článku se rozebírá, jak implementovat přísný limit využití paměti v Lua prostřednictvím vlastního alokátoru integrovaného do stavu interpretu.

Jak Lua spravuje paměť

Lua deleguje všechny operace s pamětí externímu alokátoru. Ve výchozím nastavení se používá standardní realloc/free, ale jazyk umožňuje nahradit ho vlastní implementací. To se dosahuje předáním ukazatele na funkci při vytváření lua_State. Důležité: všechny požadavky na alokaci, změnu velikosti a uvolnění paměti procházejí tímto alokátorem – včetně práce sběrače odpadu, načítání modulů a vytváření tabulek, řetězců, funkcí aj.

Funkce alokátoru má následující signaturu:

Google AdInline article slot
void *luaAlloc(void *ud, void *ptr, size_t currSize, size_t newSize);
  • ud — uživatelská data (obvykle ukazatel na stav řídícího paměti);
  • ptr — ukazatel na aktuální blok (může být NULL při alokaci nového);
  • currSize — aktuální velikost bloku nebo typ objektu (v Lua 5.2+ při ptr == NULL);
  • newSize — požadovaná nová velikost (0 znamená uvolnění).

To poskytuje plnou kontrolu nad distribucí paměti, což umožňuje realizovat její účetnictví a omezení.

Implementace omezeného alokátoru

Pro sledování objemu alokované paměti se zavádí struktura stavu:

struct LimitedAllocatorState {
    size_t used {};
    size_t limit {1 * 1024 * 1024}; // 1 MB ve výchozím nastavení
    bool limitReached {false};
    bool overflow {false};

    bool isLimitEnabled() { return limit > 0; }
    void disableLimit() { limit = 0; }
    void resetErrorFlags() { limitReached = overflow = false; }
};

Alokátor kontroluje, zda nový požadavek (used - currSize + newSize) nepřekračuje stanovený limit. Při překročení vrací nullptr, což vede k chybě v Lua (např. not enough memory).

Google AdInline article slot

Zde jsou klíčové body implementace:

  • Při newSize == 0 — uvolní se paměť a used se sníží o currSize;
  • Při ptr == NULL — alokuje se nový blok, currSize se ignoruje;
  • Ochrana před přetečením celých čísel je povinná;
  • Stav alokátoru musí přežít déle než lua_State.

Příklad funkce:

void *limitedAlloc(void *ud, void *ptr, size_t currSize, size_t newSize) {
    auto *allocState = static_cast<LimitedAllocatorState*>(ud);
    if (!allocState) return nullptr;

    if (ptr == nullptr) currSize = 0;
    if (newSize == 0) {
        if (ptr != nullptr) {
            allocState->used = (allocState->used >= currSize)
                ? allocState->used - currSize : 0;
        }
        std::free(ptr);
        return nullptr;
    }

    const size_t usedBase = (allocState->used >= currSize)
        ? allocState->used - currSize : 0;

    if (newSize > SIZE_MAX - usedBase) {
        allocState->overflow = true;
        return nullptr;
    }

    const size_t newUsed = usedBase + newSize;
    if (allocState->isLimitEnabled() && newUsed > allocState->limit) {
        allocState->limitReached = true;
        return nullptr;
    }

    void *newPtr = std::realloc(ptr, newSize);
    if (newPtr) allocState->used = newUsed;
    return newPtr;
}

Integrace s C++ runtime

Při používání knihovny sol2 (populární obal nad Lua C API) vytvoření stavu s vlastním alokátorem vypadá takto:

Google AdInline article slot
sol::state lua(sol::default_at_panic, limitedAlloc, &allocState);

Důležité je dodržet pořadí deklarace členů v třídě: stav alokátoru musí být zničen po sol::state, jinak při destrukci stavu dojde k přístupu k již uvolněné paměti.

Doporučuje se zabalit celou logiku do třídy LuaRuntime, která:

  • Spravuje životní cyklus stavu;
  • Poskytuje metody pro reset chyb a změnu limitu za běhu;
  • Umožňuje kontrolovat, zda byla překročena kvóta.

Příklad rozhraní:

class LuaRuntime {
private:
    lua::memory::LimitedAllocatorState allocatorState;
    sol::state state;

public:
    LuaRuntime(size_t memoryLimit)
        : allocatorState({.limit = memoryLimit}),
          state(sol::default_at_panic, lua::memory::limitedAlloc, &allocatorState) {}

    bool hasAllocError() {
        return allocatorState.limitReached || allocatorState.overflow;
    }

    void resetAllocErrors() {
        allocatorState.resetErrorFlags();
    }

    bool setMemoryLimit(size_t limit) {
        allocatorState.limit = limit;
        return true;
    }
};

Omezení a praktická doporučení

Několik důležitých poznámek při práci s omezeným alokátorem:

  • Limit je společný pro celý stav. Pokud používáte více pískovnic na jednom lua_State, sdílejí si jeden rozpočet paměti. Pro striktní izolaci – jeden stav na jednu pískovnici.
  • Sběrač odpadu také spotřebovává paměť. I po odstranění objektů v Lua se paměť může dočasně zvětšit během práce GC.
  • Chyby alokace ne vždy vedou k panice. Lua generuje výjimku, kterou lze zachytit přes pcall nebo obslužný program paniky.
  • Nezapomeňte resetovat vlajky chyb. Po zpracování chyby limitReached zůstává aktivní až do explicitního resetu.

Také je třeba počítat s tím, že některé operace (např. konkatenace dlouhých řetězců nebo hluboká rekurze) mohou způsobit náhlé skoky v spotřebě paměti. Testování s realistickými zátěžemi je nezbytné.

Co je důležité

  • Lua umožňuje plně kontrolovat alokaci paměti prostřednictvím vlastního alokátoru.
  • Limit paměti se aplikuje na celý stav jako celek, ne na jednotlivé skripty.
  • Při překročení limitu alokátor vrací nullptr, což vyvolá chybu v Lua.
  • Stav alokátoru musí přežít lua_State z hlediska životnosti.
  • Přetečení celých čísel a správná aritmetika jsou povinné prvky spolehlivé implementace.

— Editorial Team

Advertisement 728x90

Číst dál