替代大量异常使用的最佳方法是什么?

4

我有一个很久以前做的旧C++项目。它是一个CPU模拟器。每当代码中发生CPU故障(例如除以零、调试断点中断等)时,它只会执行throw,在我的主循环中,我有类似这样的代码:

try{
    *(uint32_t*)&op_cache=ReadDword(cCS,eip);
    (this->*Opcodes[op_cache[0]])();
    eip=(uint16_t)eip+1;
}
catch(CpuInt_excp err){
    err.code&=0x00FF;
    switch(err.code){
        case 0:
        case 1: //.....
        Int16(err.code);
        break;
        default:
        throw CpuPanic_excp("16bit Faults",(err.code|0xF000)|TRIPLE_FAULT_EXCP);
        break;
    }
}

以下是一个简单的操作码示例(凭空想象)

if(**regs16[AX]==0){
  throw CpuInt_excp(0); //throw divide by zero error
}

这段代码的基本作用是读取一个操作码,如果发生异常,则调用相应的中断(在CPU中,这只是改变EIP)。
由于这个代码段位于主循环中,try{}catch{}的开销会不断累加。这并不是过早优化,我已经对其进行了剖析,gcc的异常帮助函数(甚至没有发生任何故障和抛出异常)和辅助函数占据了长时间运行的仿真程序总执行时间的10%以上。
那么,在这种情况下,最好的替代异常的方法是什么呢?我宁愿不跟踪返回值,因为我已经编写了大量的代码,并且当函数变得非常深时,跟踪它们非常困难。

2
嗯,我建议使用setjmplongjmp,但这与许多C++特性不兼容。你可能不得不咬紧牙关,跟踪返回值,并从中吸取教训:异常是为了处理异常情况。 - ephemient
我打算尝试将try块移动到其他地方,看看是否有帮助,如果没有...那么,我想你是对的 :( - Earlz
这是 GCC 3 吗?如果是 GCC 4,您尝试过使用“-fprofile-arcs”选项吗?您的分析将告诉编译器异常不会发生。 - MSalters
我已经尝试过两种方法,但从未尝试过那个标志。 - Earlz
3个回答

3
你没有展示你的循环代码,但我猜测它的伪代码应该是:
while (some_condition) {
    // Your try..catch block:
    try {
        // Do an op
    }
    catch (CpuInt_excp err) {
        // Handle the exception
    }
}

您可以将try..catch移出一级:
done = false;
while (!done) {
    try {
        while (some_condition) {
            // Do an op
        }
        done = true;
    }
    catch (CpuInt_excp err) {
        // Handle the exception
    }
}

我在这里包含了两个循环,因为我假设如果发生异常,您希望能够继续执行(不知道在 Int16 中做什么,但我认为您允许在非 panic 异常后继续执行)。当然,如果您不需要继续执行,那么只需要一个循环。

外部循环只是在异常后重新启动事情。如果该条件不昂贵(例如,它是程序计数器或类似的东西),它可以检查与内部循环相同的条件,或者如果该条件昂贵,则可以像上面一样具有标志。


1
移动try/catch语句到内部循环之外怎么样?这会在catch中增加一些簿记工作,并且需要额外的开销来重新进入内部循环而不是从头开始,但只有在实际发生异常时才需要付出代价,而不是每次都需要。

添加外部循环以在发生异常时重新启动内部循环,并确保内部循环中所有变量实际上都在外部循环中定义,以便处理簿记和“从头开始”的问题。 - vava
@vava:是的,我也是这么想的。但只有earlz知道那是否实际可行。 - John Knoeller
是的,实际上这是以库的形式存在的,主应用程序会执行类似于 cpu->exec() 的操作来在主循环中执行单个操作码。因此,这将需要重新考虑它的工作方式... - Earlz
如果你能将你的状态保存在一个结构体(或类)中,那可能会解决问题。 - John Knoeller

0

看起来你正在将CPU状态记录在成员变量中,因为你的操作码方法不带任何参数。冒昧地说,为什么不对异常状态做同样的事情呢?这样你就不必处理返回值了。相反,你只需要让操作码函数设置异常状态、返回并让主循环检查异常状态。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接