ESP32 Arduino如何在PSRAM中分配并使用自定义结构体数组
崩溃根因
你遇到的崩溃(EXCCAUSE: 0x0000001d 存储访问禁止)是两个问题共同导致的:
ps_malloc是C风格的原始内存分配函数,不会执行C++结构体的构造函数。你的expStruct中包含String类型成员,String是Arduino封装的类,依赖构造函数初始化内部的字符串指针、容量等属性,直接用ps_malloc返回的内存操作String时,会访问随机野指针触发异常。- 若你未在Arduino IDE工具菜单中开启「PSRAM」支持,
ps_malloc会直接返回NULL,访问空指针也会触发崩溃。
另外你原来的memset(expanders, 0, sizeof(expanders));不能直接用的原因是:修改后expanders是单个指针,sizeof(expanders)得到的是4字节(32位架构下指针的大小),不是整个数组的总大小。就算你改成完整长度的memset,也会把String成员的内存直接清零,破坏类的内部结构,绝对不能使用。
解决方案
方案1:完全兼容原有访问逻辑(推荐,无需修改旧代码)
保持你原来的指针数组写法,仅修改内存分配逻辑,用C++定位new语法,既从PSRAM分配内存,又自动执行结构体构造函数:
// 全局声明和原来完全一致 expStruct * expanders[MAX_EXPANDER] EXT_RAM_ATTR; void fill_PSRAM_StructsTest(){ JsonArray hwConf = doc.as<JsonArray>(); memset(expanders, 0, sizeof(expanders)); // 指针数组初始化逻辑不变 expCounter = 0; // 每次填充前记得重置计数器,避免溢出 for(byte i = 0; i < hwConf.size() && expCounter < MAX_EXPANDER; i ++){ JsonObject module = hwConf[i]; // 定位new:先在PSRAM分配内存,再调用expStruct构造函数初始化 expanders[expCounter] = new (ps_malloc(sizeof(expStruct))) expStruct(); // 后续访问逻辑和原来完全一致,还是用->运算符 expanders[expCounter]->address = module["address"].as<uint16_t>(); expanders[expCounter]->id = module["moduleid"].as<uint16_t>(); expanders[expCounter]->serial = module["serialnr"].as<uint16_t>(); expanders[expCounter]->kind = module["kind"].as<String>(); expanders[expCounter]->name = module["name"].as<String>(); expCounter++; } }
这种方案下你原来的useStructTest等所有访问逻辑不需要做任何修改,空指针判断、->运算符访问成员都可以正常使用。
方案2:使用连续PSRAM结构体数组
如果你要使用连续内存的结构体数组,需要手动为每个元素调用构造函数初始化:
expStruct *expanders = nullptr; // 放在setup中执行一次,初始化PSRAM数组 void initPsramStructs() { expanders = (expStruct *) ps_malloc(MAX_EXPANDER * sizeof(expStruct)); // 检查PSRAM分配是否成功 if (!expanders) { Serial.println("PSRAM 分配失败,请检查PSRAM是否开启"); while(1) delay(1000); } // 手动为每个结构体执行构造函数,初始化包括String在内的所有成员 for (int i=0; i<MAX_EXPANDER; i++) { new (&expanders[i]) expStruct(); } } void fill_PSRAM_StructsTest(){ JsonArray hwConf = doc.as<JsonArray>(); expCounter = 0; for(byte i = 0; i < hwConf.size() && expCounter < MAX_EXPANDER; i ++){ JsonObject module = hwConf[i]; // 用.运算符访问成员 expanders[expCounter].address = module["address"].as<uint16_t>(); expanders[expCounter].id = module["moduleid"].as<uint16_t>(); expanders[expCounter].serial = module["serialnr"].as<uint16_t>(); expanders[expCounter].kind = module["kind"].as<String>(); expanders[expCounter].name = module["name"].as<String>(); expCounter++; } }
注意事项
- 请先确认你的ESP32已经开启PSRAM支持:Arduino IDE工具菜单中「PSRAM」选项设置为
Enabled,可以在setup中调用Serial.println(ESP.getPsramSize());打印PSRAM总大小,若输出为0说明PSRAM未正常启用。 - 若需要释放内存,需要先手动调用结构体的析构函数,再释放内存,避免String内部的堆内存泄漏:
// 方案1的释放逻辑 for (int i=0; i<MAX_EXPANDER; i++) { if (expanders[i]) { expanders[i]->~expStruct(); free(expanders[i]); expanders[i] = nullptr; } } - 所有操作都要判断
expCounter不超过MAX_EXPANDER,避免数组越界。
内容的提问来源于stack exchange,提问作者Dr.Random
相关产品推荐
相关产品推荐

