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

vector<Spell*>递归调用cast()触发段错误问题求助

段错误问题:vector<Spell*>容器遍历调用cast()方法崩溃

我有一个vector<Spell*>类型的容器spells,想调用容器首个元素的cast()函数,依次调用容器内所有Spell*对象的cast()方法,但程序执行到me->cast(me, pos, 0.0f, capacity-1, draw);时触发段错误崩溃。

之前未使用指针容器时,代码虽能运行,但调用完最后一个Spell后会触发SIGSEGV错误。

期望输出(当draw=1时):

Empty Slot cast (virtual)
Empty Slot cast (virtual)
Empty Slot cast (virtual)
Spark Bolt cast

当draw=2时:

Empty Slot cast (virtual)
Empty Slot cast (virtual)
Empty Slot cast (virtual)
Spark Bolt cast
Empty Slot cast (virtual)


出错代码

#include <iostream>
#include <vector>

using namespace std;

typedef struct Vector2 {
    float x;
    float y;
} Vector2;

class Spell {
protected:
    Vector2 pos;
    string name;
public:
    Spell() {
        pos = {1, 2};
        name = "Empty Slot";
    }
    virtual void cast(Spell *me, Vector2 from, float angle, int capacity, int draw) {
        cout << name << " cast (virtual)" << endl;
        if (draw > 0 && capacity > 0) {
            me++;
            me->cast(me, pos, 0.0f, capacity-1, draw);
        }
    }
};

class SparkBolt : public Spell {
public:
    SparkBolt () {
        pos = {0, 0};
        name = "Spark Bolt";
    }
    void cast(Spell *me, Vector2 from, float angle, int capacity, int draw) {
        cout << name << " cast" << endl;
        if (draw > 0 && capacity > 1) {
            
            me++;
            me->cast(me, pos, 0.0f, capacity-1, draw-1);
        }
    }
};

class SpellStorage {
private:
    int capacity;
    vector<Spell *> spells;
public:
    explicit SpellStorage(int capacity) {
        SpellStorage::capacity = capacity;
        for (int i = 0; i < capacity; i++) {
            spells.emplace_back(new Spell());
        }
    }
    void insertSpell(Spell *spell, int slot) {
        spells.at(slot-1) = spell;
    }
    void cast() {
        spells.at(0)->cast(spells.at(0), {3.0f, 4.0f}, 0.0f, capacity, 1);
    }
};

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main() {
    SpellStorage test = SpellStorage(5);

    test.insertSpell(new SparkBolt(), 4);
    test.cast();
    return 0;
}

错误原因

核心问题是错误地将单个Spell*指针当作数组指针操作:

  • spells.at(0)返回的是容器中第一个元素的指针,指向单个Spell对象,并非连续内存数组的起始地址。
  • 容器存储的是指针,这些指针本身的地址是连续的,但它们指向的Spell对象内存地址是分散的。
  • 执行me++会让指针跳到当前对象内存之后的非法区域,后续调用me->cast()必然访问非法内存,触发段错误。

解决方案

方案1:让SpellStorage直接负责遍历(最简方案)

修改SpellStorage的cast()方法,直接遍历容器调用每个Spell的cast(),同时通过draw参数控制调用次数:

// 修改SpellStorage的cast方法
void cast(int draw = 1) {
    int invokedCount = 0;
    for (auto spell : spells) {
        if (invokedCount >= draw) break;
        spell->cast({3.0f, 4.0f}, 0.0f);
        invokedCount++;
    }
}

// 简化Spell和SparkBolt的cast方法,移除无用参数
class Spell {
    // ... 其他代码不变
    virtual void cast(Vector2 from, float angle) {
        cout << name << " cast (virtual)" << endl;
    }
};

class SparkBolt : public Spell {
    // ... 其他代码不变
    void cast(Vector2 from, float angle) override {
        cout << name << " cast" << endl;
    }
};

调用时直接使用test.cast(1)或test.cast(2)即可得到期望输出。

方案2:传递容器引用+索引实现递归遍历

如果需要保留递归逻辑,可将容器引用和当前索引传递给cast()方法,通过索引定位下一个元素:

// 修改Spell的cast方法
class Spell {
    // ... 其他代码不变
    virtual void cast(vector<Spell*>& spells, int currentIndex, Vector2 from, float angle, int draw) {
        cout << name << " cast (virtual)" << endl;
        if (draw > 0 && currentIndex + 1 < spells.size()) {
            spells[currentIndex+1]->cast(spells, currentIndex+1, pos, 0.0f, draw - 1);
        }
    }
};

// 修改SparkBolt的cast方法
class SparkBolt : public Spell {
    // ... 其他代码不变
    void cast(vector<Spell*>& spells, int currentIndex, Vector2 from, float angle, int draw) override {
        cout << name << " cast" << endl;
        if (draw > 0 && currentIndex + 1 < spells.size()) {
            spells[currentIndex+1]->cast(spells, currentIndex+1, pos, 0.0f, draw - 1);
        }
    }
};

// 修改SpellStorage的cast方法
class SpellStorage {
    // ... 其他代码不变
    void cast(int draw = 1) {
        if (!spells.empty()) {
            spells[0]->cast(spells, 0, {3.0f, 4.0f}, 0.0f, draw);
        }
    }
};

这种方式通过合法的容器索引访问下一个元素,避免了非法内存操作。


内容的提问来源于stack exchange,提问作者Bazza491

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 09:50:45