美文网首页
solidity 学习(二) 关于变量存储位置的纠缠

solidity 学习(二) 关于变量存储位置的纠缠

作者: 真是个点子王 | 来源:发表于2020-04-15 22:12 被阅读0次
  • 这两天的学习,感觉对于solidity代码中的变量的存储问题一头雾水,头都被搞晕了。
  • 暂时有一点点思路,不知道是否完全正确,先梳理一下

EVM虚拟机


  • 自己觉得要清楚其中变量的问题,首先要明白EVM虚拟机的组件构成。
  • 这里保存两种对它的描述,一是在《精通以太坊》这本书中的,二是在以太坊黄皮书中的描述。

    《精通以太坊》:


    • 1、一个不可变的程序代码存储区ROM,加载了要执行二点智能合约的字节码;
    • 2、一个内容可变的内存(memory),它被严格地初始化为全0数值;
    • 3、一个永久的存储,它是作为以太坊状态的一部分的存在,也会被初始化为全0;
    • 4、一个数据栈。

    黄皮书:


    • EVM是一个简单的基于堆栈体系结构。机器的字(word)大小为256位(bit)(因此堆栈元素的大小也是如此)。这是为了方便Keccak256哈希和椭圆曲线计算而选择的。
    • 内存(memory)模型是一个简单的按字寻址的字节数组
    • 堆栈(stack)的最大尺寸为1024
    • 虚拟机同样还有一个独立的存储模型(storage),这在概念上类似于内存,但它不是字节(byte)数组,而是一个可寻址的字(word)数组。与内存(memory)不同的是,存储是非易失性的,并作为系统状态的一部分被维护。
    • 存储和内存中的所有位置都被初始化为0。
    • EVM不符合冯诺依曼的标准结构。它不是将程序代码存储在通常可访问的内存或存储器中,而是单独存储在虚拟ROM中,该虚拟ROM只能通过专门的指令进行交互。
    • 由于多种原因,机器可能会有异常执行,包括堆栈下溢和无效指令。机器会立即停止并将问题报告给执行代理做出相应的处理。

状态变量


  • 在solidity的文档中描述:Solidity 合约类似于面向对象语言中的类。合约中有用于数据持久化的状态变量,和可以修改状态变量的函数。 而状态变量是永久地存储在合约存储(storage)中的值。
pragma solidity ^0.4.0;

contract SimpleStorage {
    uint storedData; // 状态变量
    // ...
}
  • 以上这一点与传统编程语言的存储方式有别,感觉还挺重要的。

数据位置


  • 所有的复杂类型,即 数组 和 结构 类型,都有一个额外属性,“数据位置”,说明数据是保存在 内存(memory) 中还是 存储(storage) 中。 根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后增加关键字 storage 或 memory 进行修改。 函数参数(包括返回的参数)的数据位置默认是 memory, 局部变量的数据位置默认是 storage,状态变量的数据位置强制是 storage (这是显而易见的)。
  • 也存在第三种数据位置, calldata ,这是一块只读的,且不会永久存储的位置,用来存储函数参数。 外部函数的参数(非返回参数)的数据位置被强制指定为 calldata ,效果跟 memory 差不多。
  • 总结:
    • 强制指定的数据位置:
      • 1、外部函数的参数(不包括返回参数): calldata
      • 2、状态变量: storag
    • 默认数据位置:
      • 1、函数参数(包括返回参数): memory
      • 2、所有其它局部变量: storage
  • 关于数据位置对赋值行为的影响:
    • 1、 在 存储(storage)和 内存(memory)之间两两赋值,或者 存储(storage)向状态变量(甚至是从其它状态变量)赋值都会创建一份独立的拷贝;
    • 2、状态变量向局部变量赋值时仅仅传递一个引用,而且这个引用总是指向状态变量,因此后者改变的同时前者也会发生改变;
    • 3、从一个 内存(memory) 存储的引用类型向另一个内存(memory) 存储的引用类型赋值并不会创建拷贝。
  • 以下是solidity文档中给出实例代码:
pragma solidity ^0.4.0;

contract C {
    uint[] x; // x 的数据存储位置是 storage

    // memoryArray 的数据存储位置是 memory
    function f(uint[] memoryArray) public {
        x = memoryArray; // 将整个数组拷贝到 storage 中,可行
        var y = x;  // 分配一个指针(其中 y 的数据存储位置是 storage),可行
        y[7]; // 返回第 8 个元素,可行
        y.length = 2; // 通过 y 修改 x,可行
        delete x; // 清除数组,同时修改 y,可行
        // 下面的就不可行了;需要在 storage 中创建新的未命名的临时数组, /
        // 但 storage 是“静态”分配的:
        // y = memoryArray;
        // 下面这一行也不可行,因为这会“重置”指针,
        // 但并没有可以让它指向的合适的存储位置。
        // delete y;

        g(x); // 调用 g 函数,同时移交对 x 的引用
        h(x); // 调用 h 函数,同时在 memory 中创建一个独立的临时拷贝
    }

    function g(uint[] storage storageArray) internal {}
    function h(uint[] memoryArray) public {}
}

关于数组的问题


  • 动态数组可以创建在memory和storage中。
  • storage中的数组:
    • 1、数组有 length 成员变量表示当前数组的长度。 动态数组可以在存储storage 中通过改变成员变量 .length 改变数组大小。 并不能通过访问超出当前数组长度的方式实现自动扩展数组的长度。
    • 2、变长的存储storage 数组以及 bytes 类型(而不是 string 类型)都有一个叫做 push 的成员函数,它用来附加新的元素到数组末尾。 这个函数将返回新的数组长度。
  • memory中的数组:
    • 1、创建内存数组
pragma solidity ^0.4.16;

contract C {
    function f(uint len) public pure {
        uint[] memory a = new uint[](7);
        bytes memory b = new bytes(len);
        // 这里我们有 a.length == 7 以及 b.length == len
        a[6] = 8;
    }
}
  • 2、可使用 new 关键字在内存中创建变长数组。 与 存储storage 数组相反的是,不能通过修改成员变量 .length 改变 内存memory 数组的大小。
  • 3、 一经创建,内存memory 数组的大小就是固定的(但却是动态的,也就是说,它依赖于运行时的参数)。

相关文章

网友评论

      本文标题:solidity 学习(二) 关于变量存储位置的纠缠

      本文链接:https://www.haomeiwen.com/subject/jpnhvhtx.html