solidity中datalocation数据位置
1.memory:修饰的变量的数据存储在内存中;
2.storage:修饰的变量的数据将永久存储在区块链上。
3.calldata:一般只有在外部函数(external)的参数被强制指定为calldata,这种数据位置是只读的,不会持久化到区块链中。internal 接口中常常出现
interface IanimalEat{
function eat(string calldata myname) external returns(string memory);
}
参考: https://blog.csdn.net/yuanziwoxin/article/details/85249384
值类型赋值,总是创建独立副本。
storage也称为引用传递:
1.在storage和 memory之间(或来自 calldata)的赋值总是创建一个独立的副本。
2.从 memory到 memory的赋值仅创建引用。这意味着对一个内存变量的更改在引用相同数据的所有其他内存变量中同样有效。
3.从storage到本地storage变量的赋值也仅赋值一个引用。
函数中 uint[] y = x; 默认是storage
函数参数,uint[] memoryArray 默认是引用位置
pragma solidity ^0.4.0;
contract TestLoc {
uint[] x; // x的存储位置是storage
// memoryArray的存储位置是 memory
function f(uint[] memoryArray) public returns (uint[]) {
x = memoryArray; // 从 memory 复制到 storage
uint[] y = x; // storage 引用传递局部变量y(y 是一个 storage 引用)
y[1] = 2; // x y 都会被修改
// 错误, 不能将memory赋值给局部变量
// y = memoryArray;
g(x); // 引用传递, g可以改变x的内容
h(x); // 拷贝到memory, h无法改变x的内容
return x;
}
function g(uint[] storage storageArray) internal {
storageArray[2] = 3;
}
function h(uint[] memoryArray) public {
memoryArray[2] = 4;
}
}
所有其他到storage的赋值总是被复制。这种情况的示例是赋值给状态变量或storage结构体类型的局部变量成员,即使局部变量本身只是一个引用。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.7.0;
contract DataLocationTest {
uint[] stateVar = [1,4,5];
function foo() public{
// case 1 : 从存储中加载到内存
uint[] memory y = stateVar; // 复制 stateVar 到 y
// case 2 : from memory to storage
y[0] = 12;
y[1] = 20;
y[2] = 24;
stateVar = y; // copy the content of y to stateVar
// case 3 : from storage to storage
uint[] storage z = stateVar; // z is a pointer to stateVar
z[0] = 38;
z[1] = 89;
z[2] = 72;
}
}
在remix,debug下测试上面代码
要单步执行(Step over)代码,请单击上图红色框中的箭头。
第一种情况:
从storage 到memory的赋值
你应该首先注意到,正如我们在EVM部分中提到的, 状态(Solidity State)部分加载了storage 的stateVar的内容,当然没有局部变量。
当你单步执行时,应该看到变量 y出现在局部变量(Solidity locals)部分中。继续执行(step over),你会注意到,为了分配必要的内存空间,需要使用很多字节码
并从 storage中加载每个字,然后将其复制到 memory中。这意味着要支付更多的 gas ,因此从 storage到 memory
第二种情况:
从memory到storage的赋值
让我们研究第二种情况:从memory到storage的赋值。
当你修改完存储在memory中的副本并且想要将更改保存回storage时,可以使用它。它同样消耗大量的 gas 。如果我们用调试器步骤详细信息中的剩余 gas (remaining gas)来计算 gas 差值,则为17,083 gas 。该操作使用了四个SSTORE操作码:第一个用于存储数组大小(保持不变,消耗800个 gas ),另外三个用于更新数组的值(每个消耗了5,000个 gas )。
第三种情况
从storage到storage的赋值
现在,让我们看一下情况三:从storage到storage的赋值。这次将创建一个新的局部变量,并包含与stateVar相同的内容。如果我们查看代码的执行过程,会注意到Solidity所做的,将包含数组长度的存储的第一个插槽地址入栈。根据文档,对于动态数组,插槽位置包含数组长度,用于计算包含数组数据的插槽位置。
来比较两者的 gas 成本:
第一种是将数据复制到memory,然后更新并复制回 storage, 使用21,629 gas ,
第二种是直接创建引用并更新状态, 使用 5,085 gas 。
那么很明显第二种方法是便宜得多。
但是,如果我们像这样直接更新状态变量呢,像这样:
stateVar[0] = 12;
也有可能但是,如果你要处理映射和嵌套数据类型(我们将在后面看到),则使用storage指针可以让代码更具可读性。