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

时间:2009-08-02 18:13:35

标签: c exception-handling stack

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

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

double pop(void)
{
    if(sp > 0)
        return val[--sp];
    else {
        printf("error: stack empty\n");
        return 0.0;
    }
}
第77页的

K&R示例(上面的代码)返回0.0。但是如果用户先前在堆栈上推送了0.0,那么如何知道堆栈是否为空或者是否返回了正确的值呢?

12 个答案:

答案 0 :(得分: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;
}

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

另外,如果你走这条路,你可能想要为你的错误代码定义符号常量(或者使用the standard ones的一些),这样用户就可以区分“堆栈空”和“你给我了”空指针,dumbass“。

答案 1 :(得分:10)

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

  TRY {
    ...
    THROW(MY_EXCEPTION);
    /* Unreachable */
  } CATCH(MY_EXCEPTION) {
    ...
  } CATCH(OTHER_EXCEPTION) {
    ...
  } FINALLY {
    ...
  }

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

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

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

我不建议在严肃的代码中使用它除了你自己以外的程序员应该使用它。如果您不确切知道发生了什么,那么用这个来拍摄自己太容易了。线程,资源管理和信号处理是非玩具程序在尝试使用longjmp“例外”时会遇到的问题区域。

答案 2 :(得分:7)

您有几个选择:

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

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

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

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

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

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

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

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

答案 3 :(得分:4)

一种方法是指定如果堆栈为空,pop()具有未定义的行为。然后,您必须提供一个is_empty()函数,可以调用该函数来检查堆栈。

另一种方法是使用C ++,它有例外: - )

答案 4 :(得分:4)

这实际上是尝试使用魔术值重载返回类型以及简单可疑的界面设计的邪恶的完美示例。

我可能用来消除模糊性(因此需要“异常行为”)的一个解决方案是定义一个正确的返回类型:

struct stack{
    double* pData;
    uint32  size;
};

struct popRC{
    double value;
    uint32 size_before_pop;
};

popRC pop(struct stack* pS){
    popRC rc;
    rc.size=pS->size;
    if(rc.size){
        --pS->size;
        rc.value=pS->pData[pS->size];
    }
    return rc;
}

用法当然是:

popRC rc = pop(&stack);
if(rc.size_before_pop!=0){
    ....use rc.value

这种情况一直都在发生,但在C ++中为了避免这种歧义,人们通常会返回一个

std::pair<something,bool>

bool是一个成功指标 - 看看其中一些:

std::set<...>::insert
std::map<...>::insert

或者在界面中添加double*并返回一个(n UNOVERLOADED!)返回码,比如表示成功的枚举。

当然,没有必要在struct popRC中返回大小。它可能是

enum{FAIL,SUCCESS};

但是,由于尺寸可能对pop'er有用,所以你可以使用它。

顺便说一下,我衷心同意结构堆栈接口应该有

int empty(struct stack* pS){
    return (pS->size == 0) ? 1 : 0;
}

答案 5 :(得分:3)

在这种情况下,你通常会做一个

  • 将其留给来电者。例如这是由调用者知道pop()是否安全(例如在弹出堆栈之前调用stack-&gt; is_empty()函数),如果调用者搞砸了,那就是他的错,祝你好运。
  • 通过out参数或返回值发出错误信号。

e.g。你要么

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

}

int pop(double *d)
{
   if(sp > 0) { 
       *d = val[--sp];
       return 0;
   } else {
     return 1;

   }
}

答案 6 :(得分:2)

直接C中没有等同于例外。如果这是你想要的,你必须设计你的函数签名来返回错误信息。

C中可用的机制是:

  • 使用setjmp / longjmp
  • 的非本地getos
  • 信号

但是,这些语义都没有类似于C#(或C ++)异常的语义。

答案 7 :(得分:1)

你可以返回一个指向double的指针:

  • 非NULL - &gt;有效
  • NULL - &gt;无效

答案 8 :(得分:1)

1)您返回一个标志值以显示它失败,或者您使用TryGet语法,其中返回是成功的布尔值,而值通过输出参数传递。

2)如果这是在Windows下,则使用类似“_try”的语法,有一种操作系统级的纯C形式的异常,称为Structed Exception Handling。我提到了它,但我不推荐这种情况。

答案 9 :(得分:1)

没有人提到的东西,但它很丑陋:

int ok=0;

do
{
   /* Do stuff here */

   /* If there is an error */
   break;

   /* If we got to the end without an error */
   ok=1;

} while(0);

if (ok == 0)
{
   printf("Fail.\n");
}
else
{
   printf("Ok.\n");
}

答案 10 :(得分:0)

这里已经有了一些很好的答案,只是想提一下接近“异常”的东西,可以使用宏来完成,就像在令人敬畏的MinUnit中所做的那样(这只会返回“异常“到调用者函数”。

答案 11 :(得分:0)

setjmplongjmp和宏。已经完成了很多次 - 我所知道的最古老的实现是Eric Roberts和Mark vanderVoorde--但我目前使用的是Dave Hanson C Interfaces and Implementations的一部分,并且不受普林斯顿的影响。