Ошибка при компиляции: слишком глубокий стек

При попытке скомпилировать я получаю следующую ошибку:

«Внутренняя ошибка компилятора: слишком глубокий стек, попробуйте удалить локальные переменные».

Есть ли способ обойти это? Я не уверен, смогу ли я удалить достаточно переменных, чтобы это исправить.

Спасибо!

Ответы (6)

Вы сталкиваетесь с StackTooDeepException .

Код Solidity не кажется последовательным в количестве переменных, которые он считает проблемой, но у вас есть предел около 16 или 17. (Хотя ясно, что нижний предел 16 будет тем, который сработает. ..)

CommonSubexpressionEliminator.cpp и CompilerUtils.cpp :

assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep, try removing local variables.");

ContractCompiler.cpp :

solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables.");

Не видя вашего кода, трудно комментировать возможные решения, но стоит попробовать разделить большие функции на более мелкие.


Изменить 2019:

Очень подробное объяснение этой ошибки, и как ее можно избежать, рассмотрено в статье «Stack Too Deep» — Ошибка в Solidity .

Спасибо за предложение! Будет ли массив считаться 1 локальной переменной?
Я так думаю, да. В противном случае размеры массива также были бы ограничены 16, что не является особенно большим массивом.
Я не пытаюсь оживить это, но я должен был спросить, так как я тоже получаю эту ошибку. теперь максимальное количество локальных переменных меньше 10? У меня эта ошибка с 15 переменными
Привет @MedMansour - заметил твой другой вопрос по этому поводу. Сколько вы должны удалить, прежде чем ошибка исчезнет?
У меня не было ошибок, когда я оставлял около 8 или 9 переменных из моей структуры, но я уже переключился на передачу массива фиксированного размера или отдельных аргументов. но я не знаю, что означает локальная переменная. это только переменные в аргументах? или те, что в returns()инструкции, или сумма переменных с локальными переменными внутри функции.
Я возвращаю только 8 переменных через структуру и уже получаю эту ошибку.return ( myStruct[id].var1, myStruct[id].var2, ...);
Жесткий лимит на самом деле касается количества слотов стека, а не переменных — стек EVM имеет 1024 слота, но вы можете получить доступ только к 16/17 верхним в любой момент времени. В большинстве случаев для 1 переменной требуется 1 слот, но не всегда. Например, для динамических массивов calldata и внешних указателей функций требуется 2 (это определяется makeStackItems()в исходном коде компилятора). Также некоторые дополнительные слоты могут использоваться внутри — язык намеренно скрывает стек, и точное расположение стека следует рассматривать как деталь реализации.

Uniswap, похоже, нашел изящное решение этой проблемы. Окружите часть вашей функции скобками:

{ // scope for _token{0,1}, avoids stack too deep errors
  address _token0 = token0;
  address _token1 = token1;
  require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
  if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
  if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
  if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
  balance0 = IERC20(_token0).balanceOf(address(this));
  balance1 = IERC20(_token1).balanceOf(address(this));
}

Взгляните на UniswapV2.sol для полного контекста.

Что это меняет, как вы думаете?
Не уверен .. Я видел это в производственном коде Uniswap v2, поэтому подумал, что это должно быть хорошо.
Проблема со стеком чаще всего возникает, когда в текущей области слишком много локальных переменных/параметров. Если вы поместите некоторые объявления переменных в блок, они выйдут из области видимости, когда блок закончится, уменьшив количество переменных, которые должны оставаться в стеке после него. В конечном счете, отслеживание времени жизни локальных переменных в функции — это то, что компилятор/оптимизатор должен уметь вычислять без таких «трюков», поэтому, даже если это все еще помогает с текущим генератором кода, грядущий генератор кода на основе Yul должен быть гораздо умнее в этом.

https://github.com/ethereum/solidity/issues/267

Это зависит от того, насколько сложны выражения внутри функции, но больше 16 локальных переменных работать не будет. Эта история должна это исправить: https://www.pivotaltracker.com/n/projects/1189488/stories/99085498

История не начата.

Кажется, сейчас он завершен.
@updogliu или тот, кто знает последние новости, пожалуйста, напишите ответ.
Я склонен согласиться с @eth. Похоже, это не решено в 0.4.18.

Обходной путь для этого состоит в том, чтобы поместить локальные переменные в массив того же типа, что и в массивах EVM, занимающих только один слот стека. Таким образом, функции могут стать намного больше, прежде чем будет достигнуто ограничение в 16 слотов.

Например:

contract A {

    // This will get the error: 'stack too deep, try...'
    function deepStack
    (
        uint8 _a,
        uint8 _b,
        uint8 _c,
        uint8 _d,
        uint16 _e,
        uint16 _f,
        uint16 _g,
        uint16 _h,
        uint32 _i,
        uint32 _j,
        uint32 _k,
        uint32 _l,
        uint64 _m,
        uint64 _n,
        uint64 _o,
        uint64 _p,
        uint128 _q
    )
        public
        returns (bool success)
    {
        return true;
    }

    // This function works
    function deepStackSolution
    (
        uint8[] _aToD,
        uint16[] _eToH,
        uint32[] _iToL,
        uint64[] _mToP,
        uint128 _q
    )
        public
        returns (bool success)
    {
        return true;
    }

}

Однако имейте в виду, что возможности хранения каждого слота по-прежнему ограничены. Таким образом, предоставленное хранилище будет использоваться более эффективно. Если вы используете действительно большие числа, это также может вскоре достичь своих пределов.

Вот забавная вещь. Я просто изменил метод с publicна externalи получил сообщение:

/Users/cliff/Documents/in-app-pro-shop/contracts/SKUFactory.sol:42:23: 
CompilerError: Stack too deep, try removing local variables.
skus.push(SKU(_shopId, skuId, _skuTypeId, _price, _name, _desc, _consumable, _limited, _limit));
              ^-----^

Изменение его обратно на publicудаляет ошибку! По-видимому, это немного больше нюансов, чем просто количество локальных переменных. Вот функция:

/**
 * @notice Create a SKU (Shopkeeping Unit) for a Shop
 * @dev Can only be run by shop owner
 */
function createSKU(
    uint256 _shopId,
    uint256 _skuTypeId,
    uint256 _price,
    string _name,
    string _desc,
    bool _consumable,
    bool _limited,
    uint256 _limit
)
    public
    onlyShopOwner(_shopId)
    returns(uint256)
{
    // SKUs must have a non-zero price
    require(_price > 0);

    // Get SKU ID
    uint256 skuId = skus.length;

    // Create and store SKU Type
    skus.push(SKU(_shopId, skuId, _skuTypeId, _price, _name, _desc, _consumable, _limited, _limit));

    // Add SKU to Shop's SKU list
    shopSKUs[_shopId].push(skuId);

    // Add SKU ID to SKU Type's SKU list
    skuTypeSKUs[_skuTypeId].push(skuId);

    // Emit Event with name of the new SKU
    emit NewSKU(_shopId, skuId, _name);

    // Return the new SKU ID
    return skuId;
}

Я внес изменение в ответ на это обсуждение лучших практик для externalvs public, где объясняется (хотя и довольно туманно), как функции обрабатываются по-разному в этих случаях. Я предполагаю, что это корень того, почему нет определенного количества локальных переменных, которые вызывают эту ошибку.

Фактическая основная причина заключается в том, что речь идет о слотах стека, а переменные не обязательно отображаются на слоты 1: 1. Я думаю, что причина, по которой вы получили ошибку, заключается в том, что для строковых параметров calldata требуется два слота, а для строк памяти - только один. Выбор external, а не publicпринудительное использование параметров в calldata.

Просто упакуйте свои переменные в массив памяти. Например, предположим, что у вас есть 30 переменных локальных адресов. Вместо:

address addr1 = 0x0000000000000000000000000000000000000001;
address addr2 = 0x0000000000000000000000000000000000000002;
address addr3 = 0x0000000000000000000000000000000000000003;
// etc

Ты можешь сделать:

address[] memory joinedAddresses = new address[](30);
joinedAddresses[0] = 0x0000000000000000000000000000000000000001;
joinedAddresses[1] = 0x0000000000000000000000000000000000000002;
joinedAddresses[2] = 0x0000000000000000000000000000000000000003;
// etc

Примечание. Я тестировал этот метод в Solidity 0.8.4.