Solidity Uninitialized Storage Pointer

前言

早上做题的时候遇到了这个存储指针未初始化导致的状态变量覆盖的问题,因此来学习一下。

参考文章:
警惕!Solidity缺陷易使合约状态失控
Uninitialized Storage Pointer

原理

solidity中未初始化的storage pointer有点类似C语言中的空指针。

在传统编程语言中(如C, C++),对空指针(Null Pointer)的访问,通常会引起程序的报错或崩溃。空指针的值等于零,但是语言和底层系统也同时保证内存中地址为 0 的位置是不能存放有意义的值。而在例如 Java 或者 C# 中有 引用 的概念,但是它们都定义了一个空引用的值,“null”。空引用是一个引用的安全保护值,保证这个引用不会指向任何数据。

而solidity中的“空指针”就不是这样的了。一个简单的例子:

pragma solidity ^0.4.25;

contract bet {
    uint public num1;
    uint public num2;
    uint public num3;
    struct Feng{
        uint nn1;
        uint nn2;
        uint nn3;
    }
    function number(uint _num1, uint _num2, uint _num3) public {
        Feng feng;
        feng.nn1=_num1;
        feng.nn2=_num2;
        feng.nn3=_num3;
    }
}

定义了一个Feng结构体,在number函数中传入三个uint值,然后分别覆盖函数中的feng变量。这些代码乍一看确实没啥问题,其实问题很大。
放在Remix编译的话会报一个warning:
在这里插入图片描述
但是并不是Error,可以正常的Deploy,然后试试传个值:
在这里插入图片描述
发现变成了这样:
在这里插入图片描述
这是怎么回事呢?原因就在于:

在函数内部申明一个变量,通常默认是局部变量。但是Solidity的处理有些问题,在此处反直觉地默认让引用类型(Reference Type)变量 feng 存储位置为 storage,因此对变量 feng 的修改,作用范围是“全局”的。并且对于未初始化的storage 指针(类似传统语言中的空指针),Solidity 默认其指向 storage 的起始地址,即指向合约开头定义的状态变量。

联想一下solidity插槽的知识:
在这里插入图片描述
因此也就相当于,对feng.nn1指向slot0,feng.nn2指向slot1,feng.nn3指向slot2。
而在这个例子中,slot0是num1,slot1是num2,slot2是num3,因此改变了这三个状态变量的值。

数组同样有这样的问题:

pragma solidity ^0.4.26;

contract bet {
    uint public num1;
    uint[] public feng;
    
    function number(uint _num) public {
        uint[] tmp;
        tmp.push(_num);
        feng=tmp;
    }
}

这里的tmp默认还是storage,指向slot0,导致了num1的值被覆盖。
在这里插入图片描述

修复

实际上,这个问题只存在于solidity0.5.0之前的版本,编译器版本为0.4.26的话,报的还只是一个warning,不影响deploy;在下一个版本,0.5.0里面就变成了报error:
在这里插入图片描述
至于在0.5.0之前的版本,对于结构体的话,是使用mapping进行结构体的初始化,并使用storage进行拷贝:

pragma solidity ^0.4.26;

contract bet {
    uint public num1;
    uint public num2;
    uint public num3;
    struct Feng{
        uint nn1;
        uint nn2;
        uint nn3;
    }
    
    mapping (uint => Feng) fengs;
    
    function number(uint _id, uint _num1, uint _num2, uint _num3) public {
        Feng storage feng = fengs[_id];
        feng.nn1=_num1;
        feng.nn2=_num2;
        feng.nn3=_num3;
    }
}

对于数组的话,类似上面的那个例子,就是在函数中声明的时候进行初始化:

pragma solidity ^0.4.26;

contract bet {
    uint public num1;
    uint public num2;
    uint public num3;
    uint[] public feng;
    
    function number(uint _num) public {
        uint[] storage tmp= feng;
        tmp.push(_num);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值