关于memory,storage,calldata

gwyhonor 发布于 2026-06-05 阅读 133

对于值类型因为体积小,复制成本很低只有值拷贝这一种使用方式,而对于像string,array,struct这种类型的数据体积大,使用时依据情况要考虑是否拷贝,能不拷贝尽量不拷贝,尽量使用指针引用方式,这也是memory,storage,calldata概念存在的意义。就像下图所示那样,赋值时当两

对于值类型因为体积小,复制成本很低只有值拷贝这一种使用方式,而对于像string,array,struct这种类型的数据体积大,使用时依据情况要考虑是否拷贝,能不拷贝尽量不拷贝,尽量使用指针引用方式,这也是memory,storage,calldata 概念存在的意义。

就像下图所示那样,赋值时当两个变量的位置类型一样时,创建指针来赋值,修改其中一个变量,另一个也会被修改,因为它们共享一份数据 。当两个变量位置类型不同时,就会进行值拷贝。依据不同情况选择string,array,struct类型数据的位置类型,从而尽可能提高使用效率,节省gas。

image.png

状态变量:因为状态变量需要保存到链上的,所以默认就是storage类型。

函数参数:函数参数是struct/array/string类型时,需要区分函数的可见范围类型:

  • public:默认 memory,可选 memory/calldata,禁 storage

  • external:默认 calldata,可选 memory,禁 storage

  • internal/private:默认 memory,可选 memory/storage

    原因如下:

    1. storage 参数的本质是指针/引用 在 EVM(以太坊虚拟机)中,栈的可用空间极其有限(同一时刻只有16个槽位可访问),因此无法直接在栈上存储大型复杂数据。当函数参数被声明为 storage 时,它实际上并不会分配新的存储空间或复制数据,而是作为一个指向合约内部已存在存储变量(状态变量)的快捷方式或别名。通过该引用进行的任何修改,都会直接永久改变链上的原始状态数据。

    2. 外部交易无法传入链上存储地址 外部的调用者(如其他合约或用户钱包)只能向智能合约传递具体的值或 ABI 编码后的输入数据(即 calldata)。外部交易在发起时,根本无从知晓、也无法指定目标合约内部某个特定存储槽(Storage Slot)的物理地址。

    3. 语法层面的严格锁死 为了防止随意的公开调用引发不可预知的安全风险和资源滥用,Solidity 编译器在语法层面做出了严格的隔离:public / external 函数: 绝对不允许使用 storage 作为形参的数据位置,仅允许使用 memory 或 calldata(其中 external 强制使用只读的 calldata)。 internal / private 函数: 由于这些函数只能在当前合约或其派生合约的内部被调用,调用方能够明确知道并安全地传递链上状态变量的引用,因此只有这类内部函数才允许将参数声明为 storage。

局部变量:函数内的局部变量是struct/array/string类型时,必须明确指定位置类型,可以使用 storage,memory

映射类型: mapping 只能在合约顶层定义 → 天然是状态变量、永久存在 storage, 函数内可以声明 mapping(...) storage 变量,但一般只是把全局的mapping变量赋值用。

  • 定义 mapping:只能合约顶层(状态变量)
  • 使用 mapping:函数内可用 storage 引用,不能新建、不能传参、不能 return
  contract Test{
    mapping(address=>uint) public bal; // 顶层状态变量
    function modify() public{
        // storage局部变量:只是指针,指向链上原有mapping,不新建
        mapping(address=>uint) storage m = bal;
        m[msg.sender] = 100; // 修改会改动原状态bal
    }
}

相关文章

0 条评论