预备知识
Location 变量存储位置
EVM 访问数据是,是从三个地方访问数据:memory storage calldata
storage:使用 storage 关键字声明的变量将存储在合约的状态变量中。状态变量是永久存储在以太坊区块链上的数据。它们在合约的整个生命周期内都可以被访问和修改。storage
关键字通常用于指向已存在的状态变量或复杂的数据类型(如数组或结构体)的引用。storage
是一个特殊的关键字。
用于指向已经存在于智能合约状态中的数据。你只能声明指向这些已存在状态变量的storage
引用,而不能在函数体内创建新的storage
变量。
memory:使用 memory 关键字声明的变量将存储在临时内存中,只在函数执行期间存在。这些变量通常用于临时存储和处理函数的参数和局部变量。
calldata:使用 calldata 关键字声明的变量用于访问函数调用的输入数据(嵌入在 transaction 的数据字段)。calldata 中的数据是只读的,不能被修改。
默认情况下,函数参数和局部变量的存储位置是 memory,而状态变量的存储位置是 storage。
注:在函数参数中,memory 与 calldata 的区别,calldata 会直接指向调用函数的交易的一部分的数据字段,直接使用,交易数据肯定不可改的所以 calldata 就是只读的,不可更改。memory 会多做一些操作就是复制这个数据字段(calldata),然后作为参数执行,也就需要更多的 gas,但也无关痛痒。
交易数据字段
在以太坊中,每个交易(transaction)都包含一些特定的数据字段。以下是常见的交易数据字段:
nonce
:交易发送者(发送交易的账户)的交易序号。它用于确保每个交易都有唯一的序号,防止重放攻击。gasPrice
:交易发送者愿意支付的每单位 gas 的价格。这决定了矿工愿意为打包和执行该交易所提供的报酬。gasLimit
:交易所允许使用的最大 gas 数量。这限制了交易执行的复杂性和成本,并确保在运行复杂或计算密集型操作时不会耗尽 gas。to
:接收交易的目标地址。对于合约创建交易,该字段可以为 null。value
:以太币(Ether)的数量,作为交易的价值。该字段用于在交易中传递货币或代币。data
:交易的附加数据字段。在普通以太坊交易中,这通常为空。对于调用合约的交易,该字段包含要执行的合约方法的参数。v
,r
,s
:交易的数字签名字段。它们用于验证交易的合法性和完整性,确保交易发送者的身份和数据的不可篡改性。
这些字段组合在一起形成一个完整的交易,并通过以太坊网络进行广播和验证。交易需要被矿工打包到区块中,并通过共识算法进行验证和确认。一旦交易被包含在区块中,它就成为不可变的记录,影响了账户余额和合约状态等数据。
理解存储位置之后:
跨存储位置的赋值:值拷贝
当赋值操作涉及不同存储位置的变量时,通常会发生值拷贝。举几个例子:
-
从
storage
到memory
的赋值:此时会创建存储在memory
中的数据副本。pragma solidity ^0.8.0; contract Example { uint[] public myArray; // 存储位置为 storage function myFunction() public { uint[] memory tempArray = myArray; // 值拷贝到 memory // ... } }
-
从
memory
到storage
的赋值:此时会创建存储在storage
中的数据副本。function anotherFunction() public { uint[] memory newArray = new uint[](10); // 存储位置为 memory // ... myArray = newArray; // 值拷贝到 storage }
同一存储位置的赋值:引用拷贝
当赋值操作发生在同一存储位置的变量之间时,通常是引用拷贝。这意味着两个变量将指向存储位置中的相同数据。例如:
pragma solidity ^0.8.0;
contract Example {
uint[] public myArray; // 存储位置为 storage
function myFunction() public {
uint[] storage tempArray = myArray; // 引用拷贝,指向相同的 storage 数据
// ...
}
}
在上面的例子中,tempArray
是对myArray
的引用拷贝,任何对tempArray
的修改都会影响myArray
。
总结
了解和使用正确的存储位置(storage
、memory
、calldata
)对于高效且安全地编写Solidity智能合约至关重要。不同存储位置之间的赋值通常导致值拷贝,而同一存储位置的赋值则为引用拷贝。这些差异在处理大型数据结构时对Gas消耗和合约优化尤为重要。