简述
- Memory(内存) :
memory存储位置的参数在函数调用期间临时存储。它们在函数执行结束后不再可用。当你将参数传递给一个memory类型的函数参数时,参数的值被复制到内存中。 - Storage(存储) :
storage存储位置的参数是永久存储的,并且通常用于引用合约的状态变量。直接在函数参数中使用storage是不允许的,但你可以在函数内部创建storage引用指向状态变量。 - Calldata(调用数据) :
calldata存储位置类似于memory,但只用于外部函数的参数。它是只读的,并且更加节约Gas。当参数声明为calldata时,它们直接读取交易的输入数据,而不需要复制。(嵌入在 transaction 的数据字段)
理解calldata
预备知识--transaction数据字段
智能合约的函数调用在以太坊上实际上是通过发送交易来完成的。这些交易包含几个主要组成部分:
以下是常见的交易数据字段:
nonce:交易发送者(发送交易的账户)的交易序号。它用于确保每个交易都有唯一的序号,防止重放攻击。gasPrice:交易发送者愿意支付的每单位 gas 的价格。这决定了矿工愿意为打包和执行该交易所提供的报酬。gasLimit:交易所允许使用的最大 gas 数量。这限制了交易执行的复杂性和成本,并确保在运行复杂或计算密集型操作时不会耗尽 gas。to:接收交易的目标地址。对于合约创建交易,该字段可以为 null。value:以太币(Ether)的数量,作为交易的价值。该字段用于在交易中传递货币或代币。data:交易的附加数据字段。在普通以太坊交易中,这通常为空。对于调用合约的交易,该字段包含要执行的合约方法的参数。交易的数据部分,包括调用的<函数标识符和传递给函数的参数值>。v,r,s:交易的数字签名字段。它们用于验证交易的合法性和完整性,确保交易发送者的身份和数据的不可篡改性。
这些字段组合在一起形成一个完整的交易,并通过以太坊网络进行广播和验证。交易需要被矿工打包到区块中,并通过共识算法进行验证和确认。一旦交易被包含在区块中,它就成为不可变的记录,影响了账户余额和合约状态等数据。
函数标识符和参数值
每个以太坊智能合约函数都有一个唯一的标识符,这是通过对函数签名进行哈希运算得到的。例如,函数 myFunction(uint256) 的标识符是其签名的Keccak-256哈希值的前4个字节。当发起对合约的函数调用时,这个标识符被用来指定你想调用的函数。
参数值是调用函数时传递的实际数据。在交易中,这些参数值紧跟在函数标识符后面,并根据ABI的规则进行编码。
理解calldata--->嵌入在 transaction 的数据字段
Calldata 存储位置的特殊性
当参数被声明为calldata类型时,它直接引用了交易数据字段中的参数部分,而不是将这些数据复制到合约执行环境的内存中。这意味着:
calldata提供了对交易数据字段的直接、只读访问。- 使用
calldata作为参数存储位置更加高效,尤其在处理大量数据时,因为它避免了额外的内存分配和复制。
Memory 和 Storage 存储位置
另一方面,当参数声明为memory类型时,它们是在函数执行期间临时存在内存中的。当函数调用发生时:
memory类型的参数值是从交易数据字段复制到智能合约执行环境的内存中的。storage类型主要用于引用合约的状态变量,并且不能直接作为函数参数的存储位置。
总结
虽然所有智能合约函数调用的参数,无论它们的存储位置如何,都是从交易的数据字段中提取的,但calldata类型的参数与其他类型的参数在处理方式上有本质的区别。calldata参数直接映射到交易数据,而不涉及数据的复制,这使得它在处理较大的数据集时更加高效。这种特性使得calldata在优化Gas消耗方面非常重要。