Solidity/Remix:字符串去重函数执行数组成员赋值操作为何失败
问题根源
你遇到的回滚、调试器卡住的核心原因是Solidity内存(memory)动态数组使用规则错误:
- 所有
memory类型的动态数组在使用前必须显式指定初始长度,你声明的string[] memory deduped;没有指定长度,默认长度为0,此时直接访问deduped[0]属于数组越界访问,直接触发链上回滚。 - 其次原代码后续逻辑也存在问题:
memory数组的长度是固定的,初始化后无法像storage数组一样通过push或者直接修改.length扩容,你后续写的deduped[deduped.length]=key也会持续触发越界错误。 - 另外去重内层循环的遍历上限写的是
keys.length,应该是当前已经去重后的数组长度,否则会访问到deduped的空索引位置。
修复方案
首先需要先确定去重后数组的最大可能长度(就是原数组长度),先初始化对应长度的memory数组,记录实际去重后的元素个数,最后再截取出实际有效长度的数组返回即可。
修复后的完整代码如下:
pragma solidity >=0.7.0 <0.9.0; contract Tester { function dedupeKeys(string[] memory keys) public pure returns(string[] memory) { if (keys.length == 0) return new string[](0); // 先初始化最大可能长度的memory数组 string[] memory tempDeduped = new string[](keys.length); // 记录实际去重后的元素个数 uint dedupedCount = 0; bool found; string memory key; for(uint i=0; i<keys.length; i++) { found = false; key = keys[i]; // 只遍历已经存入去重数组的有效元素 for(uint j=0; j<dedupedCount; j++) { if(equal(tempDeduped[j], key)) { found = true; break; // 匹配到重复就提前退出,节省gas } } if (!found) { tempDeduped[dedupedCount] = key; dedupedCount++; } } // 截取实际有效长度的数组返回 string[] memory deduped = new string[](dedupedCount); for(uint i=0; i<dedupedCount; i++) { deduped[i] = tempDeduped[i]; } return deduped; } function equal(string memory _base, string memory _value) internal pure returns (bool) { bytes memory _baseBytes = bytes(_base); bytes memory _valueBytes = bytes(_value); if (_baseBytes.length != _valueBytes.length) { return false; } for (uint i = 0; i < _baseBytes.length; i++) { if (_baseBytes[i] != _valueBytes[i]) { return false; } } return true; } }
额外优化点
- 内层循环匹配到重复元素后添加了
break语句,不需要继续遍历剩余元素,减少不必要的gas消耗 - 避免了重复的越界访问风险,逻辑和原需求完全一致
- 空输入的边界处理更健壮
内容的提问来源于stack exchange,提问作者GGizmos
相关产品推荐
相关产品推荐

