使用文字初始化的状态数组与使用 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:
- Solidity 不支持内存中的动态数组,只允许在存储中使用
- 您的代码示例中的数组不是动态数组。您正在创建具有预分配内存/数组长度的内存。
长话短说:
没有push/pop因为它们不是常识中真正的动态数组
所以我猜 solidity 文档可能对此有点混乱。想象 3 种不同的场景
- 一个静态数组,你指定编译时的长度。事后无法调整大小,需要知道大小。
uint[] memory arr = new uint[](4);
- 所谓的-具有动态大小的静态数组-。你不必知道它的确切大小编译时间,这意味着你可以为数组动态分配内存?在运行时。但这仍然是一个常规数组,在内存中占用
n consecutive memory slots
。所以没有 pop
或 push
调整数组大小的方法
uint[] memory arr = new uint[](maxNumberOfTokens);
- 真正的动态数组。对于可靠性,它只能在 storage 中使用。您可以随意调整它的大小。想看solidity是如何实现动态数组的,可以看看Layout in storage,但总之;您获取数组第一个保持其长度的槽的 keccak256 散列,数组从与其散列相对应的槽开始。这样我们就不会发生碰撞
关于 arr1
、arr2
、arr3
和 arr4
之间的差异;
arr1
和arr2
是保存在存储中的状态变量,也就是说,即使事务执行完它们也会存在
但是,arr3
和arr4
是本地内存数组,执行后会被清除。
(1,2) 和 (3,4) 之间存在 巨大的 气体差异。存储使用起来要贵得多。
对内存阵列的任何操作都将使用 3 gas,(mstore
和 mload
),而从存储加载使用 200
并写入它至少使用 5000
(sload
和 sstore
)
arr2
与 arr1
相比,deploy 将使用更少的 gas,因为您节省了一些计算量,因此字节码更小。使用它们时,应该没有区别。这也适用于 arr3
和 arr4
。
使用带有 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
}
}
考虑下面的合同,有什么区别: 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:
- Solidity 不支持内存中的动态数组,只允许在存储中使用
- 您的代码示例中的数组不是动态数组。您正在创建具有预分配内存/数组长度的内存。
长话短说:
没有push/pop因为它们不是常识中真正的动态数组
所以我猜 solidity 文档可能对此有点混乱。想象 3 种不同的场景
- 一个静态数组,你指定编译时的长度。事后无法调整大小,需要知道大小。
uint[] memory arr = new uint[](4);
- 所谓的-具有动态大小的静态数组-。你不必知道它的确切大小编译时间,这意味着你可以为数组动态分配内存?在运行时。但这仍然是一个常规数组,在内存中占用
n consecutive memory slots
。所以没有pop
或push
调整数组大小的方法
uint[] memory arr = new uint[](maxNumberOfTokens);
- 真正的动态数组。对于可靠性,它只能在 storage 中使用。您可以随意调整它的大小。想看solidity是如何实现动态数组的,可以看看Layout in storage,但总之;您获取数组第一个保持其长度的槽的 keccak256 散列,数组从与其散列相对应的槽开始。这样我们就不会发生碰撞
关于 arr1
、arr2
、arr3
和 arr4
之间的差异;
arr1
和arr2
是保存在存储中的状态变量,也就是说,即使事务执行完它们也会存在
但是,arr3
和arr4
是本地内存数组,执行后会被清除。
(1,2) 和 (3,4) 之间存在 巨大的 气体差异。存储使用起来要贵得多。
对内存阵列的任何操作都将使用 3 gas,(mstore
和 mload
),而从存储加载使用 200
并写入它至少使用 5000
(sload
和 sstore
)
arr2
与 arr1
相比,deploy 将使用更少的 gas,因为您节省了一些计算量,因此字节码更小。使用它们时,应该没有区别。这也适用于 arr3
和 arr4
。
使用带有 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
}
}