You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32 Arduino如何在PSRAM中分配并使用自定义结构体数组

崩溃根因

你遇到的崩溃(EXCCAUSE: 0x0000001d 存储访问禁止)是两个问题共同导致的:

  1. ps_malloc是C风格的原始内存分配函数,不会执行C++结构体的构造函数。你的expStruct中包含String类型成员,String是Arduino封装的类,依赖构造函数初始化内部的字符串指针、容量等属性,直接用ps_malloc返回的内存操作String时,会访问随机野指针触发异常。
  2. 若你未在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++;
    }
}

注意事项

  1. 请先确认你的ESP32已经开启PSRAM支持:Arduino IDE工具菜单中「PSRAM」选项设置为Enabled,可以在setup中调用Serial.println(ESP.getPsramSize());打印PSRAM总大小,若输出为0说明PSRAM未正常启用。
  2. 若需要释放内存,需要先手动调用结构体的析构函数,再释放内存,避免String内部的堆内存泄漏:
    // 方案1的释放逻辑
    for (int i=0; i<MAX_EXPANDER; i++) {
        if (expanders[i]) {
            expanders[i]->~expStruct();
            free(expanders[i]);
            expanders[i] = nullptr;
        }
    }
    
  3. 所有操作都要判断expCounter不超过MAX_EXPANDER,避免数组越界。

内容的提问来源于stack exchange,提问作者Dr.Random

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.07 09:12:02