查看示例合同时,有时方法中声明了带有“memory”的数组,有时则没有。这有什么区别?
查看示例合同时,有时方法中声明了带有“memory”的数组,有时则没有。这有什么区别?
如果没有使用memory关键字,Solidity 将尝试在storage中声明变量。
Solidity 领头开发人员 Chriseth: "您可以将存储视为具有虚拟结构的大型数组…一个在运行时无法更改的结构-它由合同中的状态变量确定"。
也就是说,在合同创建时,存储的结构是根据您的合同级别变量声明设置的,并且不能通过将来的方法调用进行更改。但是--该存储的内容可以通过 sendTransaction 调用进行更改。这样的调用会改变“状态”,这就是为什么合同级别变量被称为“状态变量”的原因。因此,在合同级别声明的变量 uint8 storage var;
可以更改为 uint8 的任何有效值(0-255),但是类型为 uint8 的“槽”将始终存在。
如果在函数中没有使用 memory 关键字声明变量,则 Solidity 将尝试使用存储结构,这目前可以编译,但可能会产生意外结果。 memory 告诉 Solidity 在方法运行时为变量创建一块空间,在该方法中保证其大小和结构可供将来使用。
memory 不能在合同级别使用,只能在方法中使用。
请参见 FAQ 中的“什么是 memory 关键字?它有什么作用?” 条目。以下是该条目的引用:
Ethereum 虚拟机有三个区域可以存储项。
第一个是“存储”,其中包含所有合同状态变量。每个合同都有自己的存储,它在函数调用之间是持久的,而且使用起来非常昂贵。
第二个是“内存”,这用于保存临时值。它在(外部)函数调用之间被擦除,使用起来更便宜。
第三个是堆栈,用于保持小的本地变量。它几乎是免费的,但只能容纳有限数量的值。
对于绝大多数类型,你不能指定它们应该存储在哪里,因为每次使用时都会被复制。
所谓存储位置很重要的类型是结构体和数组。例如,如果你在函数调用中传递这样的变量,如果它们可以留在内存或存储器中,则不会复制它们的数据。这意味着你可以在被调用的函数中修改它们的内容,并且这些修改仍然可见于调用者。
关于存储位置,有一些默认值取决于其所涉及的变量类型:
存储在函数调用之间保留数据。它类似于计算机硬盘。状态变量是存储的数据。这些状态变量驻留在区块链上智能合约数据部分中。将变量写入存储非常昂贵,因为运行事务的每个节点都必须执行相同的操作,这使得事务更加昂贵,并使区块链更大。
内存是临时存储数据的地方,类似于RAM。函数参数和函数中的本地变量是内存数据。(如果函数是外部的,则args将存储在栈(calldata)中)。以太坊虚拟机对内存的空间有限,因此在函数调用之间存储在此处的值将被清除。
全局存储的成本为:第一次写入20000 wei,更新相同的存储位置的成本为5000 wei,读取存储的成本为200 wei。需要注意的是,这些成本是每32字节的存储成本。例如,读取64个字节将花费2 * 200 wei,即400 wei。
读写32字节数据的内存存储成本为2 wei。内存的成本远比全局存储便宜。
如您所知,访问数据库内部的数据比访问内存(会话,缓存)内部的数据更昂贵。
假设我们想要在函数中修改顶层状态变量。
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会指向与“numbers”相同的地址(类似于JavaScript中引用对象的行为)。在函数中,我向“numbers”添加了5和10,这些数字放置到Storage中。但是如果您在remix上部署代码并获取numbers[0]
,由于myArray[0]=1
,您将得到1。
myArray
定义为memory,那么情况就不同了。// 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上轻松测试。
int[] storage myArray
只是指向numbers变量的指针,而没有为myArray在存储中保留空间。那么将myArray分配给numbers的燃气成本是多少? - 0xAnonmemory
关键字有两个含义:(1) 按值复制。 (2) 将变量声明为指向新分配的复制值的指针。 storage
的意思是:(1) 不按值复制;复制引用。 (2) 将变量声明为指向新分配的-未-复制值的指针。 - Stav Alfimemory
在 Solidity 中定义了一种数据位置,可以在运行时临时保存值。Solidity 中的 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
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract StorageMemory1{
uint storageVariable;
constructor() {
}
function assignToValue(uint memoryVariable) public {
storageVariable = memoryVariable;
}
}
// 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
}
}
// 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
}
}
// 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
}
}
memory
关键字?如果内存是短暂的,那么使用它的原因是什么?合约如何调用这些函数并在部署后修改内存? - Null isTruecalldata
的区别是什么? - Qwerty