使用文字初始化的状态数组与使用 Solidity 中的 "new" 运算符初始化的另一个状态数组之间的区别?

Difference between a state array initialized using literals and another state array initialized using the "new" operator in Solidity?

考虑下面的合同,有什么区别: a) arr_1 和 arr_2 b) arr_3 和 arr_4

contract Dummy{
    uint8[] public arr_1 = new uint8[](4);
    uint8[] public arr_2 = [0,0,0,0];
 
    function f() public pure {
        uint[3] memory arr_3;
        uint[] memory arr_4 = new uint[](3);
    }
}

此外,Solidity 表示我们可以使用 new 运算符创建本地内存动态数组,如下所示:

 function f() public pure {
        uint[] memory arr = new uint[](3);
        //The following lines give error        
        //arr.push(10); //ERROR: local memory arrays dont support PUSH
        //arr.pop(); //ERROR: local memory arrays dont support POP
}

但是,它们不支持推送和弹出。那么,它们到底有多动态?

我指的Solidity版本是0.8.13

编辑 再添加一个示例,这次是 gas 成本

//Deplyment Cost: 171321
contract CostTest_0 {
    uint8[] public arr_0 = [0,0,0];
    
}

//Deplyment Cost: 172504
contract CostTest_1 {
    uint8[] public arr_1 = new uint8[](3);  //This is another way.   
}

//Deplyment Cost: 417617
contract CostTest_2 {
    uint8[] public arr_0 = [0,0,0];     //One way of declaring dynamic arrays
    uint8[] public arr_1 = new uint8[](3);  //This is another way.
    
    //31443
    function f() public {
        arr_0.push(100);
    }

    //31553    
    function g() public {
        arr_1.push(100);
    }
    //h(0) => 26188, h(1) => 26250
    function h(uint256 idx) public view returns (uint8) {
        return arr_0[idx];
    }
    //i(0) => 26144, i(1) => 26206
    function i(uint256 idx) public view returns (uint8) {
        return arr_1[idx];
    }


    //p(0) => 22030 , p(1) => 22042
    function p(uint256 idx) public pure returns (uint8) {
        uint8[] memory tmp = new uint8[](3);
        return tmp[idx];
    }
    //q(0) => 22009 , q(1) => 22021
    function q(uint256 idx) public pure returns (uint8) {
        uint8[3] memory tmp = [0, 0, 0];  
        return tmp[idx];
    }
}

Also, Solidity says that we can create local memory dynamic arrays using the new operator, like below:

  1. Solidity 不支持内存中的动态数组,只允许在存储中使用
  2. 您的代码示例中的数组不是动态数组。您正在创建具有预分配内存/数组长度的内存。

长话短说:

没有push/pop因为它们不是常识中真正的动态数组

所以我猜 solidity 文档可能对此有点混乱。想象 3 种不同的场景

  • 一个静态数组,你指定编译时的长度。事后无法调整大小,需要知道大小。
    uint[] memory arr = new uint[](4);
  • 所谓的-具有动态大小的静态数组-。你不必知道它的确切大小编译时间,这意味着你可以为数组动态分配内存?在运行时。但这仍然是一个常规数组,在内存中占用 n consecutive memory slots 。所以没有 poppush 调整数组大小的方法
    uint[] memory arr = new uint[](maxNumberOfTokens);
  • 真正的动态数组。对于可靠性,它只能在 storage 中使用。您可以随意调整它的大小。想看solidity是如何实现动态数组的,可以看看Layout in storage,但总之;您获取数组第一个保持其长度的槽的 keccak256 散列,数组从与其散列相对应的槽开始。这样我们就不会发生碰撞

关于 arr1arr2arr3arr4 之间的差异;

arr1arr2是保存在存储中的状态变量,也就是说,即使事务执行完它们也会存在

但是,arr3arr4是本地内存数组,执行后会被清除。

(1,2) 和 (3,4) 之间存在 巨大的 气体差异。存储使用起来要贵得多。

对内存阵列的任何操作都将使用 3 gas,(mstoremload),而从存储加载使用 200 并写入它至少使用 5000sloadsstore

arr2arr1 相比,deploy 将使用更少的 gas,因为您节省了一些计算量,因此字节码更小。使用它们时,应该没有区别。这也适用于 arr3arr4

使用带有 new 关键字的变体,solidity 允许您在运行时决定大小,并且这种支持自然会随着计算量的增加而产生更多的 gas 成本。例如,

contract A {
    uint256 arrSize;
    constructor(uint256 _size){
        arrSize = _size;
    }

    function giveMeAnArray() public pure returns (uint256[]){
        uint256[arrSize] memory arr; // this is not allowed
        uint256[] memory arr = new uint256[](arrSize); // but this is allowed
    }
}