存储位置
基础
默认数据位置
状态变量默认storage
内部函数参数默认memory
外部函数参数默认calldata
例如
pragma solidity ^0.8.0;
contract SimpleAssign{
// storage 类型
struct S{
string a;
uint b;
}
function assign(S s) internal{
// 函数参数默认是 memory 类型,即 s 是 memory 类型
// 局部变量默认 storage 类型
// 将一个memory类型的参数赋值给storage类型的局部变量会报错。
S tmp = s; // 报错
}
}
值转换
storage–>storage
把一个storage类型赋值给一个 storage 类型时,只是修改其指针(引用传递)。
pragma solidity ^0.8.0;
contract StorageToStorageTest{
struct S{string a;uint b;}
//默认是storage的
S s;
function storageTest(S storage s) internal{
S test = s;
test.a = "Test";
}
function call() returns (string){
storageTest(s);
return s.a; //Test
}
}
memory–>storage
分为 2 种情况: a. 将 memory–>状态变量; 即将内存中的变量拷贝到存储中(值传递)
pragma solidity ^0.8.0;
contract StorageToStorageTest{
struct S{string a;uint b;}
//默认是storage
S s;
function storageTest(S s) internal{
s = s;
s.a = "Test";
}
function call() returns (string){
storageTest(s);
return s.a;
}
}
b.将memeory–>局部变量 报错
storage–>memory
即将数据从storage拷贝到memory中
pragma solidity ^0.4.0;
contract StorageToMemory{
struct S{string a;uint b;}
// storage 类型
S s = S("storage", 1);
function storageToMemory(S storage x) internal{
S memory tmp = x;//由Storage拷贝到memory中
//memory的修改不影响storage
tmp.a = "Test";
}
function call() returns (string){
storageToMemory(s);
return s.a;//storage
}
}
memory–>memory
和storage转storage一样是引用传递
pragma solidity ^0.4.0;
contract MemoryToMemory{
struct S{string a;uint b;}
function smemoryTest(S s) internal{
S memory test = s;
test.a = "Test";
}
function call() returns (string){
//默认是storage的
S memory s = S("memory",10);
smemoryTest(s);
return s.a;//Test
}
}
Memory
概述
存储在内存中,即分配、即使用,越过作用域则不可访问,等待被回收。 内存是易失性读写字节可寻址空间。它主要用于在执行过程中存储数据,主要用于向内部函数传递参数。鉴于这是易失性区域,每个消息调用都以清除内存开始。所有位置最初都定义为零。作为calldata,内存可以按字节级别寻址,但一次只能读取32字节的字。 当我们写入以前未使用过的单词时,内存被称为“扩展”。除了写入本身的成本之外,这种扩展也有成本,前 724 字节呈线性增长,之后呈二次方增长。
特点
存储在区块链
状态变量
复杂类型的局部变量
Storage对应合约内的存储。
你可以把你不想被cloned的变量作为Storage存储。
Storage是长期有效的,但是成本比较高。
操作码
EVM 提供了三个操作码来与内存区域进行交互
MLOAD 从内存中加载一个单词到堆栈中。
MSTORE 将单词保存到内存中。
MSTORE8 将一个字节保存到内存中。
Calldata
概述
存储函数参数,它是只读的,不会永久存储的一个数据位置。外部函数的参数被强制指定为 calldata,效果与 memory 类似。
特点
calldata几乎是免费的,但是有一个长度的限制。
calldata 是一个只读的字节可寻址空间,其中保存了事务或调用的数据参数。与堆栈不同,要使用此数据,您必须指定确切的字节偏移量和要读取的字节数。
操作码
EVM 提供的用于操作调用数据的操作码
CALLDATASIZE 告诉交易数据的大小。
CALLDATALOAD 将 32 字节的事务数据加载到堆栈中。
CALLDATACOPY 将一定数量的交易数据字节复制到内存中。
常用操作
Memory变量
Memory变量临时变量(内存型),合约函数执行完成时,内存型变量被移除。 在函数参数或返回值声明时,如果返回数据的类型是变长的,那么需要加memory修饰,例如
string、
bytes、
数组、
自定义结构
等类型都需要使用memory。
function setUser(string memory _name, uint8 _age, string memory _sex) public {}
storage变量
storage的作用类似于C++中的引用传递,用storage修饰的变量等同于右值的一个分身,对其进行修改也会影响到本尊。
区别
如果数组中的结构体赋值给函数中声明的变量,使用memory修饰获得的是一个副本,也就是拷贝
function edit(uint _id) public {
//这里获取的是 一份拷贝,下面的修改不会影响数组里的结构体
Person memory p = persons[_id];
p.name = "test";
p.age = 0;
//执行结束后 persons[_id]里的name 和age并不会改变
}
如果使用storage修饰变量,那么得到的是一个引用,就是p 是指向 persons[_id] 的指针。例如
function edit(uint _id) public {
Person storage p = persons[_id];
//这里会改变数组里的结构体
p.name = "test";
p.age = 0;
//执行结束后,persons[_id]的name和age分别变为 "test"和0
}
Last updated