简体   繁体   English

C : 你如何模拟“异常”?

[英]C : How do you simulate an 'exception'?

I come from a C# background, but I'm learning C at the moment.我来自 C# 背景,但我目前正在学习 C。 In C#, when one wants to signal that an error has occurred, you throw an exception.在 C# 中,当人们想要发出发生错误的信号时,您会抛出异常。 But what do you do in C?但是你在 C 中做什么?

Say for example you have a stack with push and pop functions.例如,假设您有一个带有pushpop函数的堆栈。 What is the best way to signal that the stack is empty during a pop ?pop期间发出堆栈为空的信号的最佳方法是什么? What do you return from that function?你从那个函数返回什么?

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

K&R 's example from page 77 ( code above ) returns a 0.0 .来自第 77 页的K&R示例(上面的代码)返回0.0 But what if the user pushed a 0.0 earlier on the stack, how do you know whether the stack is empty or whether a correct value was returned?但是如果用户更早地将0.0压入堆栈,您如何知道堆栈是否为空或是否返回了正确的值呢?

Exception-like behavior in C is accomplished via setjmp/longjmp . C 中的异常行为是通过setjmp/longjmp 实现的 However, what you really want here is an error code.但是,您在这里真正想要的是错误代码。 If all values are potentially returnable, then you may want to take in an out-parameter as a pointer, and use that to return the value, like so:如果所有值都可能返回,那么您可能希望将输出参数作为指针,并使用它来返回值,如下所示:

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

Not ideal, obviously, but such are the limitations of C.显然,这并不理想,但这就是 C 的局限性。

Also, if you go this road, you may want to define symbolic constants for your error codes (or use some of the standard ones ), so that a user can distinguish between "stack empty" and "you gave me a null pointer, dumbass".此外,如果你走这条路,你可能想为你的错误代码定义符号常量(或使用一些标准常量),以便用户可以区分“堆栈为空”和“你给了我一个空指针,笨蛋”。

You could build an exception system on top of longjmp/setjmp:Exceptions in C with Longjmp and Setjmp .您可以在 longjmp/setjmp 之上构建一个异常系统:C 中的 Exceptions with Longjmp 和 Setjmp It actually works quite well, and the article is a good read as well.它实际上效果很好,这篇文章也很好读。 Here's how your code could look like if you used the exception system from the linked article:如果您使用链接文章中的异常系统,您的代码可能如下所示:

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

It's amazing what you can do with a little macros, right?用一些宏可以做的事情真是太神奇了,对吧? It's equally amazing how hard it is to figure out what the heck is going on if you don't already know what the macros do.同样令人惊讶的是,如果您还不知道宏的作用,那么弄清楚到底发生了什么事情是多么困难。

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

Note, however, that exceptions implemented in this way will still have some limitations compared to "real" exceptions in C# or C++.但是请注意,与 C# 或 C++ 中的“真实”异常相比,以这种方式实现的异常仍然会有一些限制。 A major problem is that only your code will be compatible with this exception system.一个主要问题是只有您的代码才能与此异常系统兼容。 As there is no established standard for exceptions in C, system and third party libraries just won't interoperate optimally with your homegrown exception system.由于 C 中没有既定的异常标准,系统和第三方库无法与您自己开发的异常系统进行最佳互操作。 Still, this can sometimes turn out to be a useful hack.尽管如此,这有时会证明是一个有用的技巧。

I don't recommend using this in serious code which programmers other than yourself are supposed to work with.我不建议在严肃的代码中使用它,而不是你自己的程序员应该使用它。 It's just too easy to shoot yourself in the foot with this if you don't know exactly what is going on.如果您不确切知道发生了什么,那么用这个来射击自己太容易了。 Threading, resource management, and signal handling are problem areas which non-toy programs will encounter if you attempt to use longjmp "exceptions".如果您尝试使用 longjmp “异常”,线程、资源管理和信号处理是非玩具程序将遇到的问题领域。

You have a few options:您有几个选择:

1) Magic error value. 1) 魔术误差值。 Not always good enough, for the reason you describe.由于您描述的原因,并不总是足够好。 I guess in theory for this case you could return a NaN, but I don't recommend it.我想理论上对于这种情况,您可以返回 NaN,但我不建议这样做。

2) Define that it is not valid to pop when the stack is empty. 2) 定义栈为空时弹出无效。 Then your code either just assumes it's non-empty (and goes undefined if it is), or asserts.然后您的代码要么只是假设它是非空的(如果是,则未定义),或者断言。

3) Change the signature of the function so that you can indicate success or failure: 3)更改函数的签名,以便可以指示成功或失败:

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

Document it as "If successful, returns 0 and writes the value to the location pointed to by dptr. On failure, returns a non-zero value."将其记录为“如果成功,则返回 0 并将值写入 dptr 指向的位置。失败时,返回非零值。”

Optionally, you could use the return value or errno to indicate the reason for failure, although for this particular example there is only one reason.或者,您可以使用返回值或errno来指示失败的原因,尽管对于此特定示例,只有一个原因。

4) Pass an "exception" object into every function by pointer, and write a value to it on failure. 4)通过指针将“异常”对象传递给每个函数,并在失败时向其写入值。 Caller then checks it or not according to how they use the return value.然后调用者根据他们如何使用返回值来检查它与否。 This is a lot like using "errno", but without it being a thread-wide value.这很像使用“errno”,但它不是线程范围的值。

5) As others have said, implement exceptions with setjmp/longjmp. 5) 正如其他人所说,使用 setjmp/longjmp 实现异常。 It's doable, but requires either passing an extra parameter everywhere (the target of the longjmp to perform on failure), or else hiding it in globals.这是可行的,但需要在任何地方传递一个额外的参数(longjmp 在失败时执行的目标),或者将其隐藏在全局变量中。 It also makes typical C-style resource handling a nightmare, because you can't call anything that might jump out past your stack level if you're holding a resource which you're responsible for freeing.它还使典型的 C 风格资源处理成为一场噩梦,因为如果您持有一个您负责释放的资源,您将无法调用任何可能跳出堆栈级别的内容。

One approach is to specify that pop() has undefined behaviour if the stack is empty.一种方法是在堆栈为空时指定 pop() 具有未定义的行为。 You then have to provide an is_empty() function that can be called to check the stack.然后,您必须提供一个 is_empty() 函数,可以调用该函数来检查堆栈。

Another approach is to use C++, which does have exceptions :-)另一种方法是使用 C++,它确实有例外:-)

This actually is a perfect example of the evils of trying to overload the return type with magic values and just plain questionable interface design.这实际上是试图用魔法值重载返回类型和只是简单有问题的界面设计的弊端的完美例子。

One solution I might use to eliminate the ambiguity (and thus the need for "exception like behaviour") in the example is to define a proper return type:我可能用来消除示例中的歧义(因此需要“异常行为”)的一种解决方案是定义适当的返回类型:

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

Usage of course is:用法当然是:

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

This happens ALL the time, but in C++ to avoid such ambiguities one usually just returns a这总是发生,但在 C++ 中,为了避免这种歧义,通常只返回一个

std::pair<something,bool>

where the bool is a success indicator - look at some of:其中 bool 是成功指标 - 查看以下内容:

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

Alternatively add a double* to the interface and return a(n UNOVERLOADED!) return code, say an enum indicating success.或者在接口中添加一个double*并返回一个(n UNOVERLOADED!)返回代码,比如一个指示成功的枚举。

Of course one did not have to return the size in struct popRC .当然,不必在 struct popRC返回大小。 It could have been本来可以

enum{FAIL,SUCCESS};

But since size might serve as a useful hint to the pop'er you might as well use it.但是由于大小可能是对 pop'er 的有用提示,因此您不妨使用它。

BTW, I heartily agree that the struct stack interface should have顺便说一句,我非常同意结构堆栈接口应该有

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

In cases such as this, you usually do one of在这种情况下,您通常会执行以下操作之一

  • Leave it to the caller.留给调用者。 eg it's up to the caller to know if it's safe to pop()(eg call a stack->is_empty() function before popping the stack), and if the caller messes up, it's his fault and good luck.例如,由调用者知道 pop() 是否安全(例如,在弹出堆栈之前调用 stack->is_empty() 函数),如果调用者搞砸了,那是他的错,祝你好运。
  • Signal the error via an out parameter, or return value.通过输出参数或返回值发出错误信号。

eg you either do例如你要么做

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

} }

or要么

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

   }
}

There is no equivalent to exceptions in straight C. You have to design your function signature to return error information, if that's what you want.没有等同于直接 C 中的异常。如果你想要的话,你必须设计你的函数签名来返回错误信息。

The mechanisms available in C are: C 中可用的机制是:

  • Non-local gotos with setjmp/longjmp使用 setjmp/longjmp 的非本地 goto
  • Signals信号

However, none of these has semantics remotely resembling C# (or C++) exceptions.但是,这些都没有与 C#(或 C++)异常相似的语义。

1) You return a flag value to show it failed, or you use a TryGet syntax where the return is a boolean for success while the value is passed through an output parameter. 1) 您返回一个标志值以表明它失败,或者您使用 TryGet 语法,其中返回是成功的布尔值,而该值通过输出参数传递。

2) If this is under Windows, there is an OS-level, pure C form of exceptions, called Structed Exception Handling, using syntax like "_try". 2) 如果这是在 Windows 下,则有一种操作系统级别的纯 C 形式的异常,称为结构化异常处理,使用类似“_try”的语法。 I mention it, but I do not recommend it for this case.我提到了它,但我不建议在这种情况下使用它。

setjmp , longjmp , and macros. setjmplongjmp和宏。 It's been done any number of times—the oldest implementation I know of is by Eric Roberts and Mark vanderVoorde—but the one I use currently is part of Dave Hanson's C Interfaces and Implementations and is free from Princeton.它已经完成了很多次——我所知道的最古老的实现是由 Eric Roberts 和 Mark vanderVoorde 实现的——但我目前使用的一个是 Dave Hanson 的C 接口和实现的一部分,并且不受普林斯顿的影响。

you can return a pointer to double:你可以返回一个指向双精度的指针:

  • non-NULL -> valid非 NULL -> 有效
  • NULL -> invalid NULL -> 无效

这里已经有一些很好的答案,只是想提一下接近“异常”的东西,可以使用宏来完成,就像在很棒的MinUnit 中所做的那样(这只将“异常”返回给调用者函数) .

Something that nobody has mentioned yet, it's pretty ugly though:没有人提到过的东西,虽然它很丑陋:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM