Как вернуть исходный адрес массива динамического размера в памяти Solidity?

В следующем исходном коде memOffsetвозвращает локальные значения, такие как 96, 128, в качестве значения адреса. Как мне заставить его вернуть исходный адрес, который находится в памяти, это возможно? После вызова функции f()память освобождается, как локальные переменные в C?

Полный исходный код здесь

contract C {
    function f(uint a, uint b) constant returns (uint[]) {
        assembly {
            // Create an dynamic sized array manually.
            let memOffset := mload(0x40) // 0x40 is the address where next free memory slot is stored in Solidity.
            mstore(memOffset, 0x20) // single dimensional array, data offset is 0x20
            mstore(add(memOffset, 32), 2) // Set size to 2
            mstore(add(memOffset, 64), a) // array[0] = a
            mstore(add(memOffset, 96), b) // array[1] = b
            return(memOffset, 128)
        }
    }
}

Ответы (1)

Пересмотренный предыдущий ответ не был полностью верным, поскольку это возможно в памяти, но это можно сделать только в сборке и это довольно сложно. В последнее время я довольно много смотрел на это, и ваша проблема в основном связана с указателем памяти. Есть две причины, по которым описанное выше не работает в прямой памяти; во-первых, код операции msize должен указывать на самый большой доступный индекс памяти, но не на перезаписываемый. Во-вторых, нам нужно отказаться от размера типа данных, так как он будет автоматически выделен EVM.

Как вы можете видеть из вашего примера, mload использует 0x40 (в памяти это будет ссылаться на байты 64-96), что является текущим размером выделенной памяти (msize) и указывает msize на 0x20 (байт 32).

Макет в памяти

Solidity резервирует три 256-битных слота:

0 - 64: свободное место для методов хеширования

64 - 96: текущий размер выделенной памяти (он же указатель свободной памяти)

Временное пространство может использоваться между операторами (т.е. внутри встроенной сборки).

Solidity всегда помещает новые объекты в указатель свободной памяти, и память никогда не освобождается (это может измениться в будущем).

Источник: http://solidity.readthedocs.io/en/develop/miscellaneous.html#layout-in-memory .

размер

"размер памяти, т.е. наибольший доступный индекс памяти"

Источник: http://solidity.readthedocs.io/en/develop/assembly.html#opcodes

Из исходного кода:

msize (обратите внимание, что msize также зависит от доступа к памяти для чтения)

Источник: https://github.com/ethereum/solidity/blob/18a72dbe4668e23aaf38404183b978fbbb1824d1/libevmasm/SemanticInformation.cpp#L63 .

Пример теста сброса msize:

(mstore 0x40 0)     ; Set initial MSIZE to 0x60

Источник: https://github.com/ethereum/solidity/blob/6cbb726fb8970c6cb98e9b6a2928ef612ad9d760/test/libll/EndToEndTest.cpp#L641 .

Поскольку код использует возврат, он останавливает выполнение и возвращает значение, хранящееся в памяти, на которое делается ссылка.

"завершить выполнение, вернуть данные в память [p..(p+s))"

Источник: http://solidity.readthedocs.io/en/develop/assembly.html#opcodes

Это заморозит состояние памяти и вернет значение памяти, на которое вы ссылаетесь.

Решение в памяти

Давайте посмотрим на возврат неработающего значения, изменив код на что-то вроде:

contract C {

    function c() public returns (uint[]) {
        return f(1,2);
    }

    function f(uint a, uint b) private returns (uint[] memory memOffset) {
        assembly {
             // Create an dynamic sized array manually.
             // Don't need to define the data type here as the EVM will prefix it
             mstore(add(memOffset, 0x00), 2) // Set size to 2
             mstore(add(memOffset, 0x20), a) // array[0] = a
             mstore(add(memOffset, 0x40), b) // array[1] = b
        }
    }
}

Теперь мы получаем массив с недопустимыми значениями, это связано с дополнительными операциями, выполняемыми контрактом, который перезаписывает выделенную вами память. Чтобы исправить это, нам нужно переместиться туда, куда мы записываем память, и сообщить evm, куда должен указывать msize.

contract C {

    function c() public returns (uint[]) {
        return f(1,2);
    }

    function f(uint a, uint b) private returns (uint[] memory memOffset) {
        assembly {
             // Create an dynamic sized array manually.
             // Don't need to define the data type here as the EVM will prefix it
             memOffset := msize() // Get the highest available block of memory
             mstore(add(memOffset, 0x00), 2) // Set size to 2
             mstore(add(memOffset, 0x20), a) // array[0] = a
             mstore(add(memOffset, 0x40), b) // array[1] = b
             mstore(0x40, add(memOffset, 0x60)) // Update the msize offset to be our memory reference plus the amount of bytes we're using
        }
    }
}

Вышеприведенное теперь должно возвращать правильное значение.

Предыдущий ответ:

Динамические массивы нельзя хранить в памяти из-за их недетерминированного размера. По сути, если у вас есть динамический массив с постепенно увеличивающимся и уменьшающимся хранилищем, вам придется либо постоянно перетасовывать память, рискуя перезаписать существующую память, либо резервировать большой блок памяти, что невероятно неэффективно и много газа. Другие языки программирования решили эту проблему, используя словари и структуры связанных списков, в которых вы ссылаетесь на различные области памяти, а не на последовательность памяти. Причина, по которой это возможно сделать в стеке/хранилище, заключается в том, что он оптимизирует хранилище, аналогичное связанному списку, но это не применялось к использованию в памяти. Короче говоря, массивы в Solidity далеки от завершения и нуждаются в некоторой доработке, поэтому я бы рассмотрел возможность использования других методов, таких как хранение данных в виде байтов.