¿Cómo devolver la dirección original de la matriz de tamaño dinámico en la memoria de Solidity?

En el siguiente código fuente, memOffsetdevuelve valores locales como 96, 128 como valor de dirección. ¿Cómo podría hacer que devuelva la dirección original que se encuentra en la memoria, es posible? Después f()de llamar a la función, ¿se libera la memoria como las variables locales en C?

El código fuente completo está aquí

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)
        }
    }
}

Respuestas (1)

La respuesta revisada como anterior no era completamente cierta, ya que es posible en la memoria, pero solo se puede hacer en ensamblaje y es bastante complicado. He estado mirando esto bastante recientemente y su problema se debe principalmente a un puntero de memoria. Hay dos razones por las que lo anterior no funcionará en la memoria directa; el primero es que el código de operación msize debe apuntar al índice de memoria más grande al que se accede, pero no al que se sobrescribe. La segunda es que debemos descartar el tamaño del tipo de datos, ya que EVM lo asignará automáticamente.

Como puede ver en su ejemplo, mload está usando 0x40 (en la memoria, esto hará referencia a los bytes 64-96), que es el tamaño de memoria asignado actualmente (msize) y hace que msize apunte a 0x20 (byte 32).

Diseño en memoria

Solidity reserva tres ranuras de 256 bits:

0 - 64: espacio disponible para métodos hash

64 - 96: tamaño de memoria asignado actualmente (también conocido como puntero de memoria libre)

El espacio temporal se puede usar entre declaraciones (es decir, dentro del ensamblaje en línea).

Solidity siempre coloca nuevos objetos en el puntero de memoria libre y la memoria nunca se libera (esto podría cambiar en el futuro).

Fuente: http://solidity.readthedocs.io/en/develop/miscellaneous.html#layout-in-memory

tamaño m

"tamaño de la memoria, es decir, mayor índice de memoria accedida"

Fuente: http://solidity.readthedocs.io/en/develop/assembly.html#opcodes

Desde el código fuente:

msize (tenga en cuenta que msize también depende del acceso de lectura de memoria)

Fuente: https://github.com/ethereum/solidity/blob/18a72dbe4668e23aaf38404183b978fbbb1824d1/libevmasm/SemanticInformation.cpp#L63

Ejemplo de una prueba que restablece el msize:

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

Fuente: https://github.com/ethereum/solidity/blob/6cbb726fb8970c6cb98e9b6a2928ef612ad9d760/test/liblll/EndToEndTest.cpp#L641

Debido a que el código utiliza un retorno, detiene la ejecución y devuelve el valor almacenado en la memoria al que se hace referencia.

"finalizar ejecución, devolver datos mem[p..(p+s))"

Fuente: http://solidity.readthedocs.io/en/develop/assembly.html#opcodes

Esto congelará el estado de la memoria y devolverá el valor de la memoria a la que hace referencia.

En solución de memoria

Veamos cómo devolver un valor roto modificando el código a algo como:

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
        }
    }
}

Ahora obtenemos una matriz con valores no válidos, esto se debe a que el contrato ejecuta operaciones adicionales que sobrescriben la memoria que asignó. Para solucionar esto, debemos movernos a donde estamos escribiendo en la memoria y dejar que el evm sepa hacia dónde debe apuntar 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
        }
    }
}

Lo anterior ahora debería devolver el valor correcto.

Respuesta anterior:

Las matrices dinámicas no se pueden almacenar en la memoria debido a su tamaño no determinista. Básicamente, si tiene una matriz dinámica que tiene un almacenamiento incremental que crece y se reduce, tendría que seguir barajando constantemente la memoria, correr el riesgo de sobrescribir la memoria existente o reservar un gran bloque de memoria que es increíblemente ineficiente y pesado. Otros lenguajes de programación resolvieron este problema usando diccionarios y estructuras de listas enlazadas donde hace referencia a varias ubicaciones de memoria en lugar de una secuencia de memoria. La razón por la que es posible hacerlo en la pila/almacenamiento es porque optimiza el almacenamiento de forma similar a una lista enlazada, pero esto no se ha aplicado al uso dentro de la memoria. En resumen, las matrices en Solidity están lejos de estar completas y aún necesitan algo de trabajo, por lo que consideraría usar otras técnicas, como almacenar datos como bytes.