C:你如何模拟'例外'?

And*_*ech 14 c stack exception-handling

我来自C#背景,但我现在正在学习C. 在C#中,当想要发出错误信号时,就会抛出异常.但你在C做什么?

比如说你有一个堆栈pushpop函数.什么是在a期间表示堆栈为空的最佳方式pop?你从这个功能返回什么?

double pop(void)
{
    if(sp > 0)
        return val[--sp];
    else {
        printf("error: stack empty\n");
        return 0.0;
    }
}
Run Code Online (Sandbox Code Playgroud)

K&R的第77页示例(上面的代码)返回一个0.0.但是,如果用户0.0在堆栈上推送了更早的内容,您如何知道堆栈是否为空或是否返回了正确的值?

Tyl*_*nry 15

C中的异常行为是通过setjmp/longjmp完成的.但是,您真正想要的是错误代码.如果所有值都可能是可返回的,那么您可能希望将out参数作为指针,并使用它来返回值,如下所示:

int pop(double* outval)
{
        if(outval == 0) return -1;
        if(sp > 0)
                *outval = val[--sp];
        else {
                printf("error: stack empty\n");
                return -1;
        }
        return 0;
}
Run Code Online (Sandbox Code Playgroud)

显然不是很理想,但这是C的限制.

此外,如果你走这条路,你可能想要为你的错误代码定义符号常量(或使用一些标准的代码),这样用户就可以区分"堆栈空"和"你给我一个空指针,dumbass ".

  • 我认同.真正的问题是"在C中,如果所有可能的返回值都有效,函数如何指示错误?".海报只是假设答案是某种形式的例外,因为他来自C#. (6认同)
  • 我有点不同意,因为即使我明白你的意思我也不会给那些来自java/c#的人带来这样的假设:setjmp/longjmp在任何方面都是'解决方案'到'我的异常在哪里?' (2认同)
  • Jonke是对的 - setjmp/longjmp只模拟抛出异常的一小部分.面对由此产生的奇怪控制流,您需要能够编写异常安全的代码,为此您需要析构函数(或尝试/最终).没有它,唯一可管理的方法是错误代码返回值. (2认同)

Vil*_*ari 10

您可以在longjmp/setjmp之上构建一个异常系统:使用Longjmp和Setjmp的C中的异常.它实际上工作得很好,这篇文章也很好读.如果您使用链接文章中的异常系统,那么代码就是这样的:

  TRY {
    ...
    THROW(MY_EXCEPTION);
    /* Unreachable */
  } CATCH(MY_EXCEPTION) {
    ...
  } CATCH(OTHER_EXCEPTION) {
    ...
  } FINALLY {
    ...
  }
Run Code Online (Sandbox Code Playgroud)

使用一些小宏可以做些什么,对吧?同样令人惊讶的是,如果您还不知道宏的作用,弄清楚发生了什么是多么困难.

longjmp/setjmp是可移植的:C89,C99和POSIX.1-2001指定setjmp().

但请注意,与C#或C++中的"实际"异常相比,以这种方式实现的异常仍然存在一些限制.一个主要问题是只有您的代码才能与此异常系统兼容.由于C中没有既定的例外标准,系统和第三方库只是不能与您自己开发的异常系统进行最佳互操作.不过,这有时会变成一个有用的黑客.

我不建议在严肃的代码中使用它,而不是你自己的程序员应该使用它.如果您不确切知道发生了什么,那么用这种方式射击自己就太容易了.线程,资源管理和信号处理是非玩具程序在尝试使用longjmp"例外"时会遇到的问题区域.

  • 在异常变得广泛可用之前,我实际上在C++中构建了类似的东西.我甚至通过自己的堆栈展开形式实现.幸运的是,在我们在生产代码中使用它之前,我已经意识到了. (4认同)

Ste*_*sop 7

你有几个选择:

1)魔术错误值.由于你描述的原因,并不总是足够好.我想在理论上这个案例你可以返回一个NaN,但我不推荐它.

2)定义当堆栈为空时弹出是无效的.然后你的代码只是假设它是非空的(并且如果它是未定义的),或者断言.

3)更改功能的签名,以便指示成功或失败:

int pop(double *dptr)
{
    if(sp > 0) {
            *dptr = val[--sp];
            return 0;
    } else {
            return 1;
    }
}
Run Code Online (Sandbox Code Playgroud)

将其记录为"如果成功,则返回0并将值写入dptr指向的位置.失败时,返回非零值."

或者,您可以使用返回值或errno指示失败的原因,但对于此特定示例,只有一个原因.

4)通过指针将"异常"对象传递给每个函数,并在失败时为其写入一个值.然后,呼叫者根据他们如何使用返回值来检查它.这很像使用"errno",但没有它是线程范围的值.

5)正如其他人所说,用setjmp/longjmp实现异常.它是可行的,但需要在任何地方传递额外的参数(longjmp的目标在失败时执行),或者将其隐藏在全局变量中.它还使典型的C风格资源处理成为一场噩梦,因为如果你持有一个你负责释放的资源,你就不能调用任何可能超出你的堆栈级别的东西.