C:你如何模拟一个“例外”?(C : How do you simulate an 'exc

2019-06-27 13:43发布

我来自一个C#背景,但我目前学习C。 在C#中,当一个人需要告知已发生错误,则抛出异常。 但是你怎么用C呢?

例如说你有一个堆栈pushpop功能。 什么是发出信号,表明栈中为空的最佳方式pop ? 你怎么从函数返回?

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

K&R的例如由77页( 上面的代码 )返回一个0.0 。 但是,如果用户按下一个0.0早些时候在栈上,你怎么知道堆栈是否为空或是否返回正确的值?

Answer 1:

在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的局限性

另外,如果你走这条路,你可能要定义符号常量为你的错误代码(或使用一些通用的标准 ),使用户可以与“堆空”和“你给了我一个空指针,笨蛋区分”。



Answer 2:

你可以上的longjmp / setjmp的上构建一个例外制度: 在Longjmp混合和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的“例外”非玩具程序会遇到的问题领域。



Answer 3:

您有几种选择:

1)幻误差值。 总是不够好,为你讲述他的理由。 我想在理论上对于这种情况,你可以返回一个NaN的,但我不建议这样做。

2)定义,它是无效的,当弹出堆栈为空。 那么你的代码要么只是假定它是一个非空(恢复不确定如果是),或断言。

3)改变功能的签名,这样就可以指示成功或失败:

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

文档为“如果成功,返回0,并将值的位置由DPTR指向。如果失败,返回一个非零值。”

或者,你可以使用返回值或errno来表示失败的原因,尽管这个特殊的例子中,只有一个原因。

4)传递一个“异常”对象到每一个功能由指针,和一个值写入到它上的失败。 然后来电按照他们如何使用返回值检查与否。 这是很多喜欢用“错误号”,但没有它是一个线程范围的值。

5)正如其他人所说,实现具有的setjmp / longjmp的例外。 这是可行的,但需要通过任何额外的参数随处可见(在的的longjmp的目标在失败时进行),否则隐藏它的全局变量。 这也使得典型的C风格的资源处理的噩梦,因为你不能说什么,如果你拿着你是负责释放的资源可能会跳出过去的筹码水平。



Answer 4:

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

另一种方法是使用C ++,里面确实有例外:-)



Answer 5:

这实际上是试图超载魔法值,只是普通的可疑界面设计的返回类型的邪恶的一个很好的例子。

一个解决方案,我可能用它来消除歧义(因而需要“像行为异常”)在本例中是定义一个适当的返回类型:

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>

其中布尔是成功的指标 - 看看一些:

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

或者,添加double*的界面,返回A(N UNOVERLOADED!)返回代码,说一个枚举,指示成功。

当然,人们也不必在结构返回的大小popRC 。 这本来是

enum{FAIL,SUCCESS};

但是,由于大小可作为一个有用的提示到pop'er你还不如用它。

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

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


Answer 6:

在这样的情况下,你通常做的一个

  • 离开它给调用者。 例如,它是由主叫者知道,如果它是安全的pop()方法(如弹出堆栈之前调用重新建立了新> is_empty()函数),并且调用者弄乱了,这是他的错,祝你好运。
  • 通过输出参数信号的错误,或返回值。

例如,你要么做

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;

   }
}


Answer 7:

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

在C语言中的机制是:

  • 非局部goto用的setjmp / longjmp的
  • 信号

然而,没有这些具有语义远程类似于C#(或C ++)的异常。



Answer 8:

你可以返回一个指向double:

  • 非NULL - >有效
  • NULL - >无效


Answer 9:

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

2)如果这是Windows下,有一个操作系统级,例外的纯C形式,称为Structed异常处理,使用语法像“_try”。 我提到它,但我不建议这种情况。



Answer 10:

没有人还没有提到的东西,它是相当难看,但:

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");
}


Answer 11:

目前已经有一些很好的答案在这里,只是想提一提,一些接近“异常”,可以通过使用宏来完成,如真棒已经完成MinUnit (这仅返回“例外”主叫功能) 。



Answer 12:

setjmplongjmp ,和宏。 它已经做过很多次,我该知道的最古老的实现是由Eric Roberts和马克vanderVoorde,但我目前使用的一个是大卫汉森的一部分I2C接口和实现距离普林斯顿免费。



文章来源: C : How do you simulate an 'exception'?