在 Ethereum Solidity 中,"memory" 关键字的用途是什么?

In Ethereum Solidity, what is the purpose of the "memory" keyword?

查看示例合同时,有时数组是在带有 "memory" 的方法中声明的,有时则不是。有什么区别?

如果没有 memory 关键字,Solidity 会尝试在 storage.

中声明变量

首席 Solidity 开发人员 chriseth:“您可以将存储视为具有虚拟结构的大型数组……您无法在运行时更改的结构 - 它由合约中的状态变量决定”。

也就是说,存储结构是在根据您的合约级变量声明创建合约时固定下来的,以后的方法调用无法更改。但是——可以使用 sendTransaction 调用更改该存储的内容。这样的调用会改变“状态”,这就是合约级变量被称为“状态变量”的原因。所以一个变量 uint8 storagevar;在合同级别声明的可以更改为 uint8 (0-255) 的任何有效值,但 uint8 类型值的“插槽”将始终存在。

如果你在函数中声明变量时没有使用 memory 关键字,那么 solidity 将尝试使用当前编译的存储结构,但会产生意想不到的结果。 memory 告诉 solidity 在方法运行时为变量创建一个 space 块,保证它的大小和结构以供将来在该方法中使用。

memory不能在合约层使用。仅在方法中。

请参阅常见问题解答中的 the entry "What is the memory keyword? What does it do?"。我在这里引用它:

The Ethereum Virtual Machine has three areas where it can store items.

The first is “storage”, where all the contract state variables reside. Every contract has its own storage and it is persistent between function calls and quite expensive to use.

The second is “memory”, this is used to hold temporary values. It is erased between (external) function calls and is cheaper to use.

The third one is the stack, which is used to hold small local variables. It is almost free to use, but can only hold a limited amount of values.

For almost all types, you cannot specify where they should be stored, because they are copied everytime they are used.

The types where the so-called storage location is important are structs and arrays. If you e.g. pass such variables in function calls, their data is not copied if it can stay in memory or stay in storage. This means that you can modify their content in the called function and these modifications will still be visible in the caller.

存储位置有默认值,具体取决于它涉及的变量类型:

  • 状态变量总是在存储中
  • 函数参数总是在内存中
  • 默认结构体、数组或映射类型引用存储的局部变量
  • 值类型的局部变量(即既不是数组,也不是结构,也不是映射)存储在堆栈中

memory 定义了 Solidity 中的一个数据位置,可以在运行时临时保存值。 memory Solidity中的变量只能在方法内声明,通常在方法参数中使用。它是一个短期变量,不能保存在区块链上;它仅在函数执行期间保留值,执行后其值被销毁。

看一下示例函数 f(),我在其中使用 memory 关键字声明了一个指针。它不会改变变量 User 的值,而如果它是使用 storage 声明的,它将改变存储在区块链上的变量 User 的值,并且该值不会被破坏。 .

struct User {
 string name;
}
User[] users;

function f() external {
 User memory user = users[0]; // create a pointer
 user.name = "example name" // can't change the value of struct User
}
  • 存储在函数调用之间保存数据。它就像一个电脑硬盘。状态变量是存储数据。这些状态 变量驻留在区块链上的智能合约数据部分。

  • 内存是临时存储数据的地方,就像RAM一样。函数中的函数参数和局部变量是内存数据。 (如果函数是外部函数,args 将存储在堆栈中(calldata))以太坊虚拟机的内存有限 space,因此存储在此处的值在函数调用之间被擦除。

假设我们要修改函数内的顶级状态变量。

// state variables are placed in Storage
// I am gonna mutate this inside the function
int[] public numbers

function Numbers()public{
    numbers.push(5)
    numbers.push(10)
    int[] storage myArray=numbers
    // numbers[0] will also be changed to 1
    myArray[0]=1 

   //Imagine you have an NFT contract and store the user's purchased nfts in a state variable on top-level
   // now inside a function maybe you need to delete one of the NFT's, since user sold it
   // so you will be modifying that list, inside a function using "storage"
}

int[] storage myArray=numbers 在这种情况下 myArray 将指向与“数字”相同的地址(它类似于引用对象在 javascript 中的行为方式)。在函数中,我添加了 5,然后添加了 10 到存储中的“数字”。但是如果你在 remix 上部署代码并得到 numbers[0],你将得到 1 因为 myArray[0]=1

如果你把myArray定义为记忆,那就另当别论了。

// state variables are placed in Storage
int[] public numbers

function Numbers() public{
    numbers.push(5)
    numbers.push(10)
    // we are telling Solidity make numbers local variable using "memory"
    // That reduces gas cost of your contract
    int[] memory myArray=numbers
    myArray[0]=1 

   // Now, this time maybe you want to user's NFT's where price is less than 100 $
   // so you create an array stored in "memory" INSIDE the function
   // You loop through user's Nft's and push the ones that price<100
   // then return the memory variable
   // so, after you return the memory variable, it will be deleted from the memory

}

在这种情况下,“numbers”数组被复制到内存中,myArray 现在引用一个与“numbers”地址不同的内存地址。如果您部署此代码并达到 numbers[0],您将获得 5.

我在一个简单的函数上展示了不同之处,因此可以在 Remix 上轻松测试它

当人们谈论 Solidity 中的 StorageMemory 时,他们实际上可以 指的是 Solidity 的两种不同用途这些话。这会引起很多混乱。

两个用途是:

  1. Solidity 合约存储数据的地方
  2. Solidity 变量如何存储值

每个例子:

1。 Solidity 合约存储数据的地方: 正如 Yilmaz 正确指出的那样,在第一次使用时,存储和内存可以被认为类似于硬盘驱动器(long-term,持久存储)和 RAM(临时)分别

例如:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract StorageMemory1{
    uint storageVariable;

    constructor() {
    }

    function assignToValue(uint memoryVariable) public {
        storageVariable = memoryVariable;
    }
}

在上面的示例中,'storageVariable' 的值将被保存,即使我们随着时间的推移执行不同的功能。然而,'memoryVariable'是在调用'assignToValue'函数时创建的,然后在函数完成后永远消失。

2。 Solidity 变量如何存储值: 如果您看到一个类似 'Data location must be "storage", "memory" or "calldata" for variable, but none was given.' 的错误,那么这就是它所指的内容。使用示例可以最好地理解这一点。

例如:

使用以下代码会出现上述错误:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract StorageMemory2 {
    uint[] public values;

    function doSomething() public
    {
        values.push(5);
        values.push(10);

        uint[] newArray = values; // The error will show here
    }
}

但是如果你加上这个词'memory':

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import 'hardhat/console.sol'; // to use console.log

contract StorageMemory2 {
    uint[] public values;

    function doSomething() public
    {
        values.push(5);
        values.push(10);

        console.log(values[0]); // it will log: 5

        uint[] storage newArray = values; // 'newArray' references/points to 'values'

        newArray[0] = 8888;

        console.log(values[0]); // it will log: 8888
        console.log(newArray[0]); // it will also log: 8888
    }
}

注意添加单词 'storage' 的作用:它使 'newArray' 变量引用(或指向)'values' 变量,并修改 'newArray' 也修改 'values'.

但是,如果我们改为使用'memory',请注意记录的内容:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import 'hardhat/console.sol'; // to use console.log

contract StorageMemory2 {
    uint[] public values;

    function doSomething() public
    {
        values.push(5);
        values.push(10);

        console.log(values[0]); // it will log: 5

        uint[] memory newArray = values; // 'newArray' is a separate copy of 'values'

        newArray[0] = 8888;

        console.log(values[0]); // it will log: 5
        console.log(newArray[0]); // it will log: 8888
    }
}

使用内存创建一个 copy 变量,它不引用 'values' 数组。

如果您有兴趣,'calldata' 可用于传递变量 read-only:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

contract CallDataExample {
    uint[] public values;

    function doSomething() public
    {
        values.push(5);
        values.push(10);

        modifyArray(values);
    }

    function modifyArray(uint[] calldata arrayToModify) pure private {
        arrayToModify[0] = 8888; // you will get an error saying the array is read only
    }
}