在 Ethereum Solidity,“记忆”关键词的用途是什么?

在查看样例契约时,有时数组是在具有“内存”的方法中声明的,有时则不是?

46962 次浏览

如果没有 记忆关键字,Solidy 将尝试在 储藏室中声明变量。

领导 Solidy dev chriseth: “你可以把存储想象成一个具有虚拟结构的大型数组... ... 一个在运行时不能改变的结构——它是由契约中的状态变量决定的。”。

也就是说,在基于契约级变量声明创建契约时,存储结构是固定的,不能被以后的方法调用更改。但是——该存储的内容可以通过 sendTransaction 调用进行更改。这样的调用改变“状态”,这就是契约级变量被称为“状态变量”的原因。因此,在契约级别声明的变量 uint8 storage var;可以更改为任何有效值 uint8(0-255) ,但是类型 uint8的值的“槽”始终存在。

如果在函数中声明变量时没有使用 记忆关键字,那么可靠性将尝试使用当前编译的存储结构,但是可能会产生意外的结果。记忆告诉可靠性在方法运行时为变量创建一个空间块,保证它的大小和结构以备将来在该方法中使用。

内存 不能在契约级别使用。只能在方法中使用。

请参阅常见问题解答中的 条目“内存关键字是什么? 它是做什么的?”,我在这里引用它:

以太虚拟机有三个区域可以存储物品。

第一个是“存储”,其中包含所有契约状态变量。每个契约都有自己的存储空间,而且在函数调用之间是持久的,并且使用成本很高。

第二个是“内存”,用于保存临时值。它在(外部)函数调用之间被擦除,并且使用成本更低。

第三个是堆栈,用于保存小的局部变量。它几乎可以免费使用,但只能保存有限数量的值。

对于几乎所有类型,您都不能指定它们应该存储在哪里,因为每次使用它们时都会复制它们。

所谓的存储位置非常重要的类型是结构体和数组。例如,如果在函数调用中传递这些变量,如果它们的数据可以保留在内存中或保留在存储器中,那么它们的数据就不会被复制。这意味着您可以在调用的函数中修改它们的内容,并且这些修改在调用者中仍然可见。

存储位置的默认值取决于所涉及的变量类型:

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

memory在 Soliity 中定义了一个数据位置,这个位置可以在运行时临时保存该值。Solid 中的 memory变量只能在方法中声明,通常用于方法参数中。它是一个短期变量,不能保存在区块链中; 它只在函数执行期间保存该值,并在执行后销毁该值。

看一下示例函数 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))以太虚拟机的内存空间有限,因此存储在这里的值在函数调用之间被擦除。

第一次写作的全球存储成本是20,000魏, 5000微元用于更新同一存储位置,200微元用于 阅读存储。需要注意的是,这些成本是每32 字节的存储空间。例如,读取64字节将花费2 * 200位, 就是四百魏。

读写32字节的内存存储开销 数据是2微,内存的成本比全局存储便宜得多。

假设我们要修改 功能。

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

在这种情况下,myArray 将指向与“ number”相同的地址(类似于 javascript 中引用对象的行为)。在函数中,我给存储中的“数字”加了5,然后是10。但是如果您在 remix 上部署代码并获得 numbers[0],那么由于 myArray[0]=1,您将获得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


}

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

  • 通过将存储变量复制到内存中,我们可以防止状态变量发生不必要的变化。每次客户端调用公共函数都会修改存储变量,想象一下如果成千上万的客户端调用同一个函数,你是如何跟踪状态变量的

我在一个简单的函数上展示了差异,这样就可以很容易地在 Remix 上进行测试

当人们在《实体》中谈论 储藏室记忆时,他们实际上可以是 指这些词的两种不同用法。这会造成很多困惑。

这两种用途是:

  1. Soliity 契约在哪里存储数据
  2. Solid 变量如何存储值

例如:

1.正如 Yilmaz 正确地指出的那样,在第一次使用时,存储和内存可以被认为分别类似于硬盘(长期的,持久的存储)和 RAM (临时的)。

例如:

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


contract StorageMemory1{
uint storageVariable;


constructor() {
}


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

在上面的示例中,“ storageVariable将被保存,即使我们执行不同的函数随着时间的推移”的值。但是,当调用“ configToValue”函数和 然后在函数完成后永远消失。

2.如果你看到一个错误,比如数据位置必须是变量的“存储”、“内存”或“调用数据”,但是没有给出。那么这就是它所指的。最好通过一个例子来理解这一点。

例如:

如果使用下面的代码,就会得到上面的错误:

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

但如果你加上“记忆”这个词:

// 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’

然而,如果我们 而是使用“记忆”,注意什么被记录:

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

使用内存创建一个 收到变量,该变量不引用“ value”数组。

如果您感兴趣,可以使用‘ Calldata’将变量作为只读传递:

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