[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-6-upravlenie-pamyatyu-yul-mstore-mload":3},{"article":4,"author":54},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":7,"meta_description":16,"focus_keyword":17,"og_image":18,"canonical_url":18,"robots_meta":19,"created_at":15,"updated_at":15,"tags":20,"category_name":34,"related_articles":35},"d0000000-0000-0000-0000-000000000206","a0000000-0000-0000-0000-000000000012","Deep EVM #6: Управление памятью в Yul — mstore, mload и свободный указатель","deep-evm-6-upravlenie-pamyatyu-yul-mstore-mload","Практическое руководство по управлению памятью в Yul: операции mstore\u002Fmload, свободный указатель памяти, scratch space, аллокация массивов и паттерны безопасной работы с памятью.","## Память EVM через призму Yul\n\nВ Solidity управление памятью полностью автоматизировано. Компилятор сам отслеживает свободный указатель, аллоцирует массивы и структуры, копирует данные. Но когда вы переходите на Yul, вы берёте всё это на себя. Каждый байт, каждое смещение, каждая аллокация — ваша ответственность.\n\nЭто даёт огромную мощь и гибкость. Вы можете избежать ненужных аллокаций, переиспользовать память, оптимизировать расположение данных. Но одна ошибка — и вы перезапишете критические данные, испортите свободный указатель или получите некорректные результаты.\n\n## mstore и mload: основы\n\nДве главные операции с памятью в EVM:\n\n```yul\nassembly {\n    \u002F\u002F mstore(offset, value) — записать 32 байта\n    mstore(0x00, 0xDEADBEEF)\n    \u002F\u002F Память: [00..1f] = 0x00000000000000000000000000000000000000000000000000000000DEADBEEF\n\n    \u002F\u002F mload(offset) — прочитать 32 байта\n    let val := mload(0x00)\n    \u002F\u002F val = 0x00000000000000000000000000000000000000000000000000000000DEADBEEF\n\n    \u002F\u002F mstore8(offset, value) — записать 1 байт\n    mstore8(0x00, 0xFF)\n    \u002F\u002F Первый байт теперь 0xFF, остальные 31 байт не изменились\n}\n```\n\nВажные детали:\n- `mstore` всегда пишет 32 байта, даже если значение маленькое. Оно дополняется нулями слева.\n- `mload` всегда читает 32 байта.\n- Операции могут перекрываться: `mstore(0x10, value)` перекроет часть данных по смещениям 0x00 и 0x20.\n- Стоимость: 3 газа за операцию + квадратичная стоимость расширения памяти.\n\n## Раскладка памяти Solidity\n\nКогда вы пишете Yul внутри Solidity, вы работаете в контексте раскладки памяти Solidity:\n\n```\n\u002F\u002F Раскладка памяти Solidity:\n\u002F\u002F 0x00 - 0x3f: Scratch space (рабочее пространство)\n\u002F\u002F 0x40 - 0x5f: Свободный указатель памяти\n\u002F\u002F 0x60 - 0x7f: Нулевой слот\n\u002F\u002F 0x80+:       Свободная память\n```\n\n### Scratch Space (0x00 - 0x3f)\n\nПервые 64 байта — это рабочая область для временных вычислений. Solidity использует её для хеширования и промежуточных операций. В Yul вы можете свободно использовать эту область, не опасаясь конфликтов — при условии, что вы не вызываете Solidity-функции между записью и чтением.\n\n```yul\nassembly {\n    \u002F\u002F Безопасно: используем scratch space для keccak256\n    mstore(0x00, address())\n    mstore(0x20, 1) \u002F\u002F слот маппинга\n    let hash := keccak256(0x00, 0x40)\n}\n```\n\n### Свободный указатель (0x40)\n\nСамый критический элемент раскладки памяти. Свободный указатель хранит адрес начала неиспользованной памяти:\n\n```yul\nassembly {\n    \u002F\u002F Чтение свободного указателя\n    let freePtr := mload(0x40)\n    \u002F\u002F Начальное значение: 0x80 (после зарезервированных 128 байт)\n\n    \u002F\u002F Аллокация 64 байт\n    let myData := freePtr\n    mstore(0x40, add(freePtr, 64)) \u002F\u002F Обновление указателя\n\n    \u002F\u002F Теперь можно писать в myData\n    mstore(myData, 42)\n    mstore(add(myData, 32), 100)\n}\n```\n\n**Критическое правило:** если вы аллоцируете память через свободный указатель в Yul, вы ДОЛЖНЫ его обновить. Иначе следующая аллокация Solidity перезапишет ваши данные.\n\n## Паттерны работы с памятью\n\n### Паттерн 1: Аллокация массива в памяти\n\n```yul\nassembly {\n    function allocateArray(length) -> ptr {\n        ptr := mload(0x40)\n        \u002F\u002F Первые 32 байта — длина массива\n        mstore(ptr, length)\n        \u002F\u002F Обновляем свободный указатель: 32 (длина) + 32 * length (элементы)\n        mstore(0x40, add(ptr, add(32, mul(length, 32))))\n    }\n\n    function setElement(arrayPtr, index, value) {\n        \u002F\u002F Смещение: arrayPtr + 32 (длина) + index * 32\n        let offset := add(add(arrayPtr, 32), mul(index, 32))\n        mstore(offset, value)\n    }\n\n    function getElement(arrayPtr, index) -> value {\n        let offset := add(add(arrayPtr, 32), mul(index, 32))\n        value := mload(offset)\n    }\n\n    \u002F\u002F Использование\n    let arr := allocateArray(5)\n    setElement(arr, 0, 100)\n    setElement(arr, 1, 200)\n    let first := getElement(arr, 0) \u002F\u002F 100\n}\n```\n\n### Паттерн 2: Работа без аллокации\n\nСамый газоэффективный подход — вообще не аллоцировать память, используя scratch space:\n\n```yul\nassembly {\n    \u002F\u002F Вместо аллокации массива — используем scratch space\n    \u002F\u002F для вычисления хеша маппинга\n    function getBalanceSlot(account) -> slot {\n        mstore(0x00, account)\n        mstore(0x20, 3) \u002F\u002F слот маппинга balances\n        slot := keccak256(0x00, 0x40)\n    }\n\n    \u002F\u002F Чтение баланса одной операцией\n    let balance := sload(getBalanceSlot(caller()))\n}\n```\n\nЭтот паттерн не обновляет свободный указатель и не расширяет память — 0 дополнительных затрат.\n\n### Паттерн 3: Копирование calldata в память\n\n```yul\nassembly {\n    \u002F\u002F Копируем calldata в память для обработки\n    let size := calldatasize()\n    let ptr := mload(0x40)\n    calldatacopy(ptr, 0, size)\n    mstore(0x40, add(ptr, size))\n\n    \u002F\u002F Теперь данные доступны в памяти по ptr\n    let firstWord := mload(ptr)\n}\n```\n\n### Паттерн 4: Формирование calldata для внешнего вызова\n\nОдин из самых частых случаев использования Yul — формирование calldata для внешних вызовов без накладных расходов `abi.encodeWithSelector`:\n\n```yul\nassembly {\n    \u002F\u002F Вызов balanceOf(address) на ERC-20 токене\n    \u002F\u002F Используем scratch space — не нужна аллокация\n    mstore(0x00, 0x70a0823100000000000000000000000000000000000000000000000000000000)\n    mstore(0x04, account)\n\n    \u002F\u002F call(gas, addr, value, argsOffset, argsSize, retOffset, retSize)\n    let success := staticcall(gas(), token, 0x00, 0x24, 0x00, 0x20)\n\n    if iszero(success) {\n        revert(0, 0)\n    }\n\n    let balance := mload(0x00)\n}\n```\n\nОбратите внимание: мы перезаписываем scratch space после вызова, что допустимо.\n\n### Паттерн 5: Эффективная конкатенация байт\n\n```yul\nassembly {\n    function concatBytes32(a, b) -> result {\n        \u002F\u002F Аллоцируем 64 байта\n        result := mload(0x40)\n        mstore(result, a)\n        mstore(add(result, 32), b)\n        mstore(0x40, add(result, 64))\n    }\n\n    function hashPair(a, b) -> hash {\n        \u002F\u002F Без аллокации — используем scratch space\n        mstore(0x00, a)\n        mstore(0x20, b)\n        hash := keccak256(0x00, 0x40)\n    }\n}\n```\n\n## Квадратичная стоимость расширения памяти\n\nКогда вы обращаетесь к памяти за пределами текущей верхней границы, EVM взимает дополнительную плату. Формула:\n\n```\ncost = (words^2 \u002F 512) + (3 * words)\n```\n\nгде `words = ceil(highest_byte \u002F 32)`.\n\nДля практического понимания:\n\n```yul\nassembly {\n    \u002F\u002F Первые 724 байта (~22 слова): ~линейная стоимость, ~3 газа\u002Fслово\n    mstore(0x80, 1)    \u002F\u002F Первое расширение: ~3 газа\n\n    \u002F\u002F 10 КБ памяти: 1160 газа — приемлемо\n    mstore(0x2800, 1)  \u002F\u002F 10240 байт\n\n    \u002F\u002F 100 КБ памяти: 29600 газа — дорого\n    mstore(0x19000, 1) \u002F\u002F 102400 байт\n\n    \u002F\u002F 1 МБ памяти: 2 001 000 газа — запредельно\n    mstore(0x100000, 1) \u002F\u002F 1048576 байт\n}\n```\n\nВажный вывод: **никогда не аллоцируйте больше памяти, чем нужно**. Если вам нужно обработать большой массив, обрабатывайте его по частям.\n\n## Безопасность памяти\n\nНачиная с Solidity 0.8.13, компилятор поддерживает флаг «memory-safe assembly»:\n\n```solidity\nassembly (\"memory-safe\") {\n    \u002F\u002F Компилятор доверяет, что этот блок:\n    \u002F\u002F 1. Работает только с scratch space (0x00-0x3f) для временных данных\n    \u002F\u002F 2. Аллоцирует через свободный указатель и обновляет его\n    \u002F\u002F 3. Не перезаписывает Solidity-данные в памяти\n\n    let ptr := mload(0x40)\n    mstore(ptr, 42)\n    mstore(0x40, add(ptr, 32))\n}\n```\n\nЕсли блок помечен как memory-safe, компилятор может применить дополнительные оптимизации (stack-to-memory, размещение переменных в памяти вместо стека). Без этого флага компилятор вынужден быть консервативным.\n\n## Распространённые ошибки\n\n### 1. Забыли обновить свободный указатель\n\n```yul\n\u002F\u002F ОШИБКА: свободный указатель не обновлён\nassembly {\n    let ptr := mload(0x40)\n    mstore(ptr, data)\n    \u002F\u002F Следующая аллокация Solidity перезапишет data!\n}\n\n\u002F\u002F ПРАВИЛЬНО:\nassembly {\n    let ptr := mload(0x40)\n    mstore(ptr, data)\n    mstore(0x40, add(ptr, 32)) \u002F\u002F Обязательно обновить!\n}\n```\n\n### 2. Перезапись свободного указателя\n\n```yul\n\u002F\u002F ОШИБКА: случайная запись по адресу 0x40\nassembly {\n    mstore(0x40, someValue) \u002F\u002F Это перезапишет свободный указатель!\n}\n```\n\n### 3. Невыровненные чтения\n\n```yul\nassembly {\n    \u002F\u002F mload всегда читает 32 байта\n    \u002F\u002F Если вам нужен только 1 байт:\n    let byte_val := byte(0, mload(offset))\n\n    \u002F\u002F Или для N байт:\n    let masked := and(mload(offset), 0xFFFF) \u002F\u002F последние 2 байта\n}\n```\n\n## Возврат данных из Yul\n\nДля возврата данных из функции через Yul используйте опкод `return`:\n\n```yul\nassembly {\n    \u002F\u002F Возврат одного значения uint256\n    mstore(0x00, 42)\n    return(0x00, 0x20) \u002F\u002F return(offset, size)\n\n    \u002F\u002F Возврат нескольких значений\n    mstore(0x00, value1)\n    mstore(0x20, value2)\n    return(0x00, 0x40)\n\n    \u002F\u002F Возврат динамических данных (bytes)\n    let ptr := mload(0x40)\n    mstore(ptr, 0x20) \u002F\u002F смещение до данных\n    mstore(add(ptr, 0x20), length) \u002F\u002F длина\n    \u002F\u002F ... копируем данные ...\n    return(ptr, add(0x40, length))\n}\n```\n\n## Заключение\n\nУправление памятью в Yul — это балансирование между производительностью и безопасностью. Scratch space (0x00-0x3f) — ваш лучший друг для временных вычислений. Свободный указатель (0x40) — критический элемент, который нельзя повредить. Квадратичная стоимость расширения — ограничение, которое нужно всегда учитывать.\n\nВ следующей статье мы рассмотрим газоэффективные циклы и условные конструкции в Yul — как итерировать по массивам, обрабатывать данные и реализовывать сложную логику с минимальными затратами газа.","\u003Ch2 id=\"evm-yul\">Память EVM через призму Yul\u003C\u002Fh2>\n\u003Cp>В Solidity управление памятью полностью автоматизировано. Компилятор сам отслеживает свободный указатель, аллоцирует массивы и структуры, копирует данные. Но когда вы переходите на Yul, вы берёте всё это на себя. Каждый байт, каждое смещение, каждая аллокация — ваша ответственность.\u003C\u002Fp>\n\u003Cp>Это даёт огромную мощь и гибкость. Вы можете избежать ненужных аллокаций, переиспользовать память, оптимизировать расположение данных. Но одна ошибка — и вы перезапишете критические данные, испортите свободный указатель или получите некорректные результаты.\u003C\u002Fp>\n\u003Ch2 id=\"mstore-mload\">mstore и mload: основы\u003C\u002Fh2>\n\u003Cp>Две главные операции с памятью в EVM:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F mstore(offset, value) — записать 32 байта\n    mstore(0x00, 0xDEADBEEF)\n    \u002F\u002F Память: [00..1f] = 0x00000000000000000000000000000000000000000000000000000000DEADBEEF\n\n    \u002F\u002F mload(offset) — прочитать 32 байта\n    let val := mload(0x00)\n    \u002F\u002F val = 0x00000000000000000000000000000000000000000000000000000000DEADBEEF\n\n    \u002F\u002F mstore8(offset, value) — записать 1 байт\n    mstore8(0x00, 0xFF)\n    \u002F\u002F Первый байт теперь 0xFF, остальные 31 байт не изменились\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Важные детали:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Ccode>mstore\u003C\u002Fcode> всегда пишет 32 байта, даже если значение маленькое. Оно дополняется нулями слева.\u003C\u002Fli>\n\u003Cli>\u003Ccode>mload\u003C\u002Fcode> всегда читает 32 байта.\u003C\u002Fli>\n\u003Cli>Операции могут перекрываться: \u003Ccode>mstore(0x10, value)\u003C\u002Fcode> перекроет часть данных по смещениям 0x00 и 0x20.\u003C\u002Fli>\n\u003Cli>Стоимость: 3 газа за операцию + квадратичная стоимость расширения памяти.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"solidity\">Раскладка памяти Solidity\u003C\u002Fh2>\n\u003Cp>Когда вы пишете Yul внутри Solidity, вы работаете в контексте раскладки памяти Solidity:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>\u002F\u002F Раскладка памяти Solidity:\n\u002F\u002F 0x00 - 0x3f: Scratch space (рабочее пространство)\n\u002F\u002F 0x40 - 0x5f: Свободный указатель памяти\n\u002F\u002F 0x60 - 0x7f: Нулевой слот\n\u002F\u002F 0x80+:       Свободная память\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Scratch Space (0x00 - 0x3f)\u003C\u002Fh3>\n\u003Cp>Первые 64 байта — это рабочая область для временных вычислений. Solidity использует её для хеширования и промежуточных операций. В Yul вы можете свободно использовать эту область, не опасаясь конфликтов — при условии, что вы не вызываете Solidity-функции между записью и чтением.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Безопасно: используем scratch space для keccak256\n    mstore(0x00, address())\n    mstore(0x20, 1) \u002F\u002F слот маппинга\n    let hash := keccak256(0x00, 0x40)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Свободный указатель (0x40)\u003C\u002Fh3>\n\u003Cp>Самый критический элемент раскладки памяти. Свободный указатель хранит адрес начала неиспользованной памяти:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Чтение свободного указателя\n    let freePtr := mload(0x40)\n    \u002F\u002F Начальное значение: 0x80 (после зарезервированных 128 байт)\n\n    \u002F\u002F Аллокация 64 байт\n    let myData := freePtr\n    mstore(0x40, add(freePtr, 64)) \u002F\u002F Обновление указателя\n\n    \u002F\u002F Теперь можно писать в myData\n    mstore(myData, 42)\n    mstore(add(myData, 32), 100)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\u003Cstrong>Критическое правило:\u003C\u002Fstrong> если вы аллоцируете память через свободный указатель в Yul, вы ДОЛЖНЫ его обновить. Иначе следующая аллокация Solidity перезапишет ваши данные.\u003C\u002Fp>\n\u003Ch2 id=\"\">Паттерны работы с памятью\u003C\u002Fh2>\n\u003Ch3>Паттерн 1: Аллокация массива в памяти\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    function allocateArray(length) -&gt; ptr {\n        ptr := mload(0x40)\n        \u002F\u002F Первые 32 байта — длина массива\n        mstore(ptr, length)\n        \u002F\u002F Обновляем свободный указатель: 32 (длина) + 32 * length (элементы)\n        mstore(0x40, add(ptr, add(32, mul(length, 32))))\n    }\n\n    function setElement(arrayPtr, index, value) {\n        \u002F\u002F Смещение: arrayPtr + 32 (длина) + index * 32\n        let offset := add(add(arrayPtr, 32), mul(index, 32))\n        mstore(offset, value)\n    }\n\n    function getElement(arrayPtr, index) -&gt; value {\n        let offset := add(add(arrayPtr, 32), mul(index, 32))\n        value := mload(offset)\n    }\n\n    \u002F\u002F Использование\n    let arr := allocateArray(5)\n    setElement(arr, 0, 100)\n    setElement(arr, 1, 200)\n    let first := getElement(arr, 0) \u002F\u002F 100\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Паттерн 2: Работа без аллокации\u003C\u002Fh3>\n\u003Cp>Самый газоэффективный подход — вообще не аллоцировать память, используя scratch space:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Вместо аллокации массива — используем scratch space\n    \u002F\u002F для вычисления хеша маппинга\n    function getBalanceSlot(account) -&gt; slot {\n        mstore(0x00, account)\n        mstore(0x20, 3) \u002F\u002F слот маппинга balances\n        slot := keccak256(0x00, 0x40)\n    }\n\n    \u002F\u002F Чтение баланса одной операцией\n    let balance := sload(getBalanceSlot(caller()))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Этот паттерн не обновляет свободный указатель и не расширяет память — 0 дополнительных затрат.\u003C\u002Fp>\n\u003Ch3>Паттерн 3: Копирование calldata в память\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Копируем calldata в память для обработки\n    let size := calldatasize()\n    let ptr := mload(0x40)\n    calldatacopy(ptr, 0, size)\n    mstore(0x40, add(ptr, size))\n\n    \u002F\u002F Теперь данные доступны в памяти по ptr\n    let firstWord := mload(ptr)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Паттерн 4: Формирование calldata для внешнего вызова\u003C\u002Fh3>\n\u003Cp>Один из самых частых случаев использования Yul — формирование calldata для внешних вызовов без накладных расходов \u003Ccode>abi.encodeWithSelector\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Вызов balanceOf(address) на ERC-20 токене\n    \u002F\u002F Используем scratch space — не нужна аллокация\n    mstore(0x00, 0x70a0823100000000000000000000000000000000000000000000000000000000)\n    mstore(0x04, account)\n\n    \u002F\u002F call(gas, addr, value, argsOffset, argsSize, retOffset, retSize)\n    let success := staticcall(gas(), token, 0x00, 0x24, 0x00, 0x20)\n\n    if iszero(success) {\n        revert(0, 0)\n    }\n\n    let balance := mload(0x00)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Обратите внимание: мы перезаписываем scratch space после вызова, что допустимо.\u003C\u002Fp>\n\u003Ch3>Паттерн 5: Эффективная конкатенация байт\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    function concatBytes32(a, b) -&gt; result {\n        \u002F\u002F Аллоцируем 64 байта\n        result := mload(0x40)\n        mstore(result, a)\n        mstore(add(result, 32), b)\n        mstore(0x40, add(result, 64))\n    }\n\n    function hashPair(a, b) -&gt; hash {\n        \u002F\u002F Без аллокации — используем scratch space\n        mstore(0x00, a)\n        mstore(0x20, b)\n        hash := keccak256(0x00, 0x40)\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Квадратичная стоимость расширения памяти\u003C\u002Fh2>\n\u003Cp>Когда вы обращаетесь к памяти за пределами текущей верхней границы, EVM взимает дополнительную плату. Формула:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>cost = (words^2 \u002F 512) + (3 * words)\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>где \u003Ccode>words = ceil(highest_byte \u002F 32)\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Для практического понимания:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Первые 724 байта (~22 слова): ~линейная стоимость, ~3 газа\u002Fслово\n    mstore(0x80, 1)    \u002F\u002F Первое расширение: ~3 газа\n\n    \u002F\u002F 10 КБ памяти: 1160 газа — приемлемо\n    mstore(0x2800, 1)  \u002F\u002F 10240 байт\n\n    \u002F\u002F 100 КБ памяти: 29600 газа — дорого\n    mstore(0x19000, 1) \u002F\u002F 102400 байт\n\n    \u002F\u002F 1 МБ памяти: 2 001 000 газа — запредельно\n    mstore(0x100000, 1) \u002F\u002F 1048576 байт\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Важный вывод: \u003Cstrong>никогда не аллоцируйте больше памяти, чем нужно\u003C\u002Fstrong>. Если вам нужно обработать большой массив, обрабатывайте его по частям.\u003C\u002Fp>\n\u003Ch2 id=\"\">Безопасность памяти\u003C\u002Fh2>\n\u003Cp>Начиная с Solidity 0.8.13, компилятор поддерживает флаг «memory-safe assembly»:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-solidity\">assembly (\"memory-safe\") {\n    \u002F\u002F Компилятор доверяет, что этот блок:\n    \u002F\u002F 1. Работает только с scratch space (0x00-0x3f) для временных данных\n    \u002F\u002F 2. Аллоцирует через свободный указатель и обновляет его\n    \u002F\u002F 3. Не перезаписывает Solidity-данные в памяти\n\n    let ptr := mload(0x40)\n    mstore(ptr, 42)\n    mstore(0x40, add(ptr, 32))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Если блок помечен как memory-safe, компилятор может применить дополнительные оптимизации (stack-to-memory, размещение переменных в памяти вместо стека). Без этого флага компилятор вынужден быть консервативным.\u003C\u002Fp>\n\u003Ch2 id=\"\">Распространённые ошибки\u003C\u002Fh2>\n\u003Ch3>1. Забыли обновить свободный указатель\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">\u002F\u002F ОШИБКА: свободный указатель не обновлён\nassembly {\n    let ptr := mload(0x40)\n    mstore(ptr, data)\n    \u002F\u002F Следующая аллокация Solidity перезапишет data!\n}\n\n\u002F\u002F ПРАВИЛЬНО:\nassembly {\n    let ptr := mload(0x40)\n    mstore(ptr, data)\n    mstore(0x40, add(ptr, 32)) \u002F\u002F Обязательно обновить!\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>2. Перезапись свободного указателя\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">\u002F\u002F ОШИБКА: случайная запись по адресу 0x40\nassembly {\n    mstore(0x40, someValue) \u002F\u002F Это перезапишет свободный указатель!\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>3. Невыровненные чтения\u003C\u002Fh3>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F mload всегда читает 32 байта\n    \u002F\u002F Если вам нужен только 1 байт:\n    let byte_val := byte(0, mload(offset))\n\n    \u002F\u002F Или для N байт:\n    let masked := and(mload(offset), 0xFFFF) \u002F\u002F последние 2 байта\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"yul\">Возврат данных из Yul\u003C\u002Fh2>\n\u003Cp>Для возврата данных из функции через Yul используйте опкод \u003Ccode>return\u003C\u002Fcode>:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-yul\">assembly {\n    \u002F\u002F Возврат одного значения uint256\n    mstore(0x00, 42)\n    return(0x00, 0x20) \u002F\u002F return(offset, size)\n\n    \u002F\u002F Возврат нескольких значений\n    mstore(0x00, value1)\n    mstore(0x20, value2)\n    return(0x00, 0x40)\n\n    \u002F\u002F Возврат динамических данных (bytes)\n    let ptr := mload(0x40)\n    mstore(ptr, 0x20) \u002F\u002F смещение до данных\n    mstore(add(ptr, 0x20), length) \u002F\u002F длина\n    \u002F\u002F ... копируем данные ...\n    return(ptr, add(0x40, length))\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Заключение\u003C\u002Fh2>\n\u003Cp>Управление памятью в Yul — это балансирование между производительностью и безопасностью. Scratch space (0x00-0x3f) — ваш лучший друг для временных вычислений. Свободный указатель (0x40) — критический элемент, который нельзя повредить. Квадратичная стоимость расширения — ограничение, которое нужно всегда учитывать.\u003C\u002Fp>\n\u003Cp>В следующей статье мы рассмотрим газоэффективные циклы и условные конструкции в Yul — как итерировать по массивам, обрабатывать данные и реализовывать сложную логику с минимальными затратами газа.\u003C\u002Fp>\n","ru","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.419860Z","Практическое руководство по управлению памятью в Yul: mstore, mload, свободный указатель, scratch space и паттерны безопасной работы с памятью EVM.","Yul управление памятью mstore",null,"index, follow",[21,26,30],{"id":22,"name":23,"slug":24,"created_at":25},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":27,"name":28,"slug":29,"created_at":25},"c0000000-0000-0000-0000-000000000020","Gas Optimization","gas-optimization",{"id":31,"name":32,"slug":33,"created_at":25},"c0000000-0000-0000-0000-000000000018","Yul","yul","Блокчейн",[36,42,48],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":34,"published_at":41},"de000000-0000-0000-0000-000000000013","Уровень интероперабельности Ethereum: как 55+ L2 становятся одной сетью","uroven-interoperabelnosti-ethereum-kak-55-l2-stanovyatsya-odnoj-setyu","У Ethereum 55+ роллапов Layer 2, фрагментирующих ликвидность и пользовательский опыт. Уровень интероперабельности Ethereum — объединяющий кросс-роллап-мессаджинг, общие секвенсоры и based-роллапы — призван объединить их в единую компонуемую сеть.","2026-03-28T10:44:36.068675Z",{"id":43,"title":44,"slug":45,"excerpt":46,"locale":12,"category_name":34,"published_at":47},"de000000-0000-0000-0000-000000000012","ZK-доказательства за пределами роллапов: верифицируемый AI-инференс на Ethereum","zk-dokazatelstva-za-predelami-rollapov-verificiruemyj-ai-inferens-ethereum","Доказательства с нулевым разглашением — это уже не только инструмент масштабирования. В 2026 году zkML обеспечивает верифицируемый AI-инференс в блокчейне, ZK-копроцессоры переносят тяжёлые вычисления оффчейн с ончейн-верификацией, а новые системы доказательств SP1 и Jolt делают это практичным.","2026-03-28T10:44:36.026310Z",{"id":49,"title":50,"slug":51,"excerpt":52,"locale":12,"category_name":34,"published_at":53},"dd000000-0000-0000-0000-000000000013","EIP-7702 на практике: создание потоков смарт-аккаунтов после Pectra","eip-7702-na-praktike-sozdanie-potokov-smart-akkauntov-posle-pectra","EIP-7702 позволяет любому EOA Ethereum временно действовать как смарт-контракт в рамках одной транзакции. Вот как реализовать пакетные транзакции, спонсирование газа и социальное восстановление с помощью нового примитива абстракции аккаунтов.","2026-03-28T10:44:35.357227Z",{"id":13,"name":55,"slug":56,"bio":57,"photo_url":18,"linkedin":18,"role":58,"created_at":59,"updated_at":59},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]