Solidity中有个特殊的操作符delete
用于释放空间,因为区块链做为一种公用资源,为避免大家滥用。且鼓励主动对空间的回收,释放空间将会返还一些gas
。
delete
1关键字的作用是对某个类型值a
赋予初始值。比如如果删除整数delete a
等同于a = 0
。
1. 删除基本类型
对于基本类型,使用delete
会设置为对应的初始值:
bool b = true;//false
uint i = 100;//0
address addr = msg.sender;//0x0
bytes memory varBy = "123";//0x0
string memory str = "hello world!";//""
删除bool
类型是false
,变长字节数组是0x0
。string
则是空串。
删除枚举
删除枚举类型时,会将其值重置为序号为0的值。
pragma solidity ^0.4.0;
contract DeleteEnum{
enum Light{RED, GREEN, YELLOW}
Light light;
function f() returns (Light){
light = Light.GREEN;
delete light;
return light;
}
}
上面的例子中,删除light
后,light
将被置为序号为0的值。
删除函数
尝试了一下删除函数,会报错Error: Expression has to be an lvalue.
,看来我们不能删除函数。
2. 删除复杂类型
删除结构体
删除一个结构体,会将其中的所有成员变量一一置为初值,我们来看一个例子。
pragma solidity ^0.4.0;
contract DeleteStruct{
struct S{
uint a;
string b;
bytes c;
}
S s;
function delStruct() returns (uint, string, bytes){
s = S(10, "Hello world!", "abc");
//删除结构体将重置所有元素
delete s;
return (s.a, s.b, s.c);
}
}
在上面的例子中,我们声明了结构体s
,调用delete s
,结构体的值将变为其对应类型uint
,string
,bytes
的初始值0
,空串和0x0
。
删除映射
映射是一个特殊的存在,由于映射的键并不总是能有效遍历(数据结构没有提供接口,也并不总是需要关心所有键是什么),所存的键的数量往往是非常大的,所以我们并不能直接删除一个映射。
//Unary operator delete cannot be applied to type mapping(address => uint256)
//delete map;
如果直接删除一个映射会报错Unary operator delete cannot be applied
但我们可以指定键来删除映射中的某一项:
map[msg.sender] = 100;
//可以按key删除map
delete map[msg.sender];
删除结构体中的映射
如果删除一个结构体时,其中含有映射类型,会跳过映射类型。我们来看一个删除含映射的结构体示例:
pragma solidity ^0.4.0;
contract MappingStructDelete{
struct MapStruct{
mapping(address => uint) m;
uint i;
}
MapStruct ms;
function delMapping() returns (uint, uint){
ms = MapStruct(200);
ms.m[msg.sender] = 2000;
//删除一个结构体
delete ms;
return (ms.m[msg.sender], ms.i);
}
}
上面的示例中,删除结构体ms
,并没有影响其中映射ms.m
的值。
3. 删除数组
对于定长数组,删除时,是将数组内所有元素置为初值。
bytes32 by = "123";//0x0000000000000000000000000000000000000000000000000000000000000000
而对于变长数组时,则是将长度置为0。
pragma solidity ^0.4.0;
contract DeleteDynamicArray{
function delDynamicArr() returns (uint){
uint[] memory a = new uint[](7);
a[0] = 100;
a[1] = 200;
delete a;
return (a.length);
}
}
上述的代码a.length
将返回长度为0
。
删除数组的一个元素
我们也可以删除数组的一个元素,有一点违反直觉的是,删除一个元素后,数组会留个空隙在那里。比如三个元素的数组,删除了第二个元素,只是将第二个元素置为了初始值,其它没变。
pragma solidity ^0.4.0;
contract DeleteArrayEle{
function delArrEle() returns (uint, uint, uint){
uint[] memory arr = new uint[](3);
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
delete arr[1];
return (arr[0], arr[1], arr[2]);
}
}
上述的代码运行后,将返回1
,0
,3
。删除只是赋值,并没有移动元素。
4. gas使用的考虑
上文中,我们了解到,删除时会忽略映射,以及数组的某个元素被删除后,并不会自动整理数组。这些看起来很不符合常理,其实是基于对gas
限制的考虑。因为如果映射或数组非常大的情况下,删除或维护它们将变得非常消耗gas
2。
不过,清理空间,可以获得gas
的返还。但无特别意义的数组的整理和删除,只会消耗更多gas
,需要在业务实现上进行权衡。
清理的最佳实践
由于本身并未提供对映射这样的大对象的清理,所以存储并遍历它们来进行清理,显得特别消耗gas
。一种实践就是能复用就复用,一般不主动清理。下面是一个数组的插入实现,比如增加一个计数器,直接忽略已使用过的位置3。
uint numElements = 0;
uint[] array;
function insert(uint value) {
if(numElements == array.length) {
array.length += 1;
}
array[numElements++] = value;
}
function clear() {
numElements = 0;
}
上面的例子中,我们在数组新增时,直接忽略掉已使用过的槽位。而在代码内,我们使用numElements
来代替array.length
,以获取当前数组所在的位置。
如果这种大对象是在某个事件发生时,一次性使用,然后需要回收的。一个更有效的方式是,在发生某个事件时,创建一个新合约,在新合约完成逻辑,完成后,让合约suicide
。清理合约占用空间返还的gas
就退还给了调用者,来节省主动遍历删除消耗的额外gas
。
5. 删除的注意事项
删除本质是对一个变量赋初值。所以我们删除storage
的引用时会报错,因为storage
的引用并没有自己已分配的存储空间,所以不能对storage的引用直接赋初值。
pragma solidity ^0.4.0;
contract DeleteStorageRef{
struct S{}
S s;
function DelStorageRef(){
S storageRef = s;
//Error: Unary operator delete cannot be applied to type struct DeleteStorageRef.S storage pointer
//delete storageRef;
delete s;
}
}
上面的例子中,删除storageRef
会报错4。
关于作者
专注基于以太坊(Ethereum)的相关区块链(Blockchain)技术,了解以太坊,Solidity,Truffle,web3.js。
个人博客: http://tryblockchain.org
版权所有,转载注明出处
个人微信号:TryBlockchain
参考资料
-
delete的语法原文介绍 http://solidity.readthedocs.io/en/develop/types.html#delete ↩
-
一些关于删除数组元素不自动移动元素的讨论。 https://github.com/ethereum/solidity/issues/414 ↩
-
建议的方案来自这里: https://ethereum.stackexchange.com/questions/3373/how-to-clear-large-arrays-without-blowing-the-gas-limit ↩
-
关于storage不能直接赋值,详见关于数据位置的说明: http://me.tryblockchain.org/solidity-data-location.html ↩
处于某些特定的环境下,可以看到评论框,欢迎留言交流^_^。