【问题标题】:ANSI C equivalent of try/catch?ANSI C 等效于 try/catch?
【发布时间】:2011-04-15 07:48:13
【问题描述】:

我正在使用一些 C 代码,并且在代码运行时发现了错误,但对如何进行正确的 try/catch(如在 C# 或 C++ 中)知之甚少。

例如在 C++ 中我会这样做:

try{
//some stuff
}
catch(...)
{
//handle error
}

但在 ANSI C 中我有点迷茫。我尝试了一些在线搜索,但我没有看到有关如何实现它的足够信息/我想我会在这里问,以防有人能指出我正确的方向。

这是我正在使用的代码(相当简单的递归方法),并希望使用 try/catch(或等效的错误处理结构)进行包装。

但是我的主要问题只是如何在 ANSI C 中进行 try/catch...实现/示例不必是递归的。

void getInfo( int offset, myfile::MyItem * item )
{
    ll::String myOtherInfo = item->getOtherInfo();
    if( myOtherInfo.isNull() )
        myOtherInfo = "";
    ll::String getOne = "";
    myfile::Abc * abc = item->getOrig();
    if( abc != NULL )
    {
        getOne = abc->getOne();
    }
    for( int i = 0 ; i < offset ; i++ )
    {
             printf("found: %d", i);
    }
    if( abc != NULL )
        abc->release();
    int childCount = item->getChildCount();
    offset++;
    for( int i = 0 ; i < childCount ; i++ )
        getInfo( offset, item->getChild(i) );
    item->release();
}

【问题讨论】:

  • nicemice.net/cexcept 可能有用的东西
  • 此代码不是 C、ansi 或其他代码。 C 没有:: 范围运算符。
  • C 没有异常处理机制。所有的错误处理通常都是通过返回值和 errnum 变量来完成的。顺便说一句,最好获得一些关于如何在 C 中正确完成错误处理的详细专家 cmets :)
  • @Steve:说得好。我的回答是专门针对 C 的,而不是任何形式的 C++,不管 C 多么类似。
  • 我所知道的唯一可以编译@aiden 发布的代码的语言是 C++。如果是 C++,你可以使用try/catch。如果您实际上必须编写 ANSI C 代码,那么除了答案中提出的建议之外,您似乎还必须重写代码以真正成为有效的 C。

标签: c++ c exception-handling try-catch ansi-c


【解决方案1】:

一般来说,你不会。

可以使用setjmplongjmp 构建与try/catch 非常相似的东西,尽管C 中没有析构函数或堆栈展开这样的东西,所以RAII 是不可能的。您甚至可以使用所谓的“清理堆栈”(例如 Symbian/C++)来近似 RAII,尽管它不是一个非常接近的近似值,而且需要做很多工作。

在 C 中指示错误或失败的常用方法是返回一个指示成功状态的值。调用者检查返回值并采取相应的行动。例如,请参阅标准 C 函数:printfreadopen,了解如何指定函数。

在混合 C 和 C++ 代码时,您必须确保 C++ 异常永远不会到达 C 代码。在编写将从 C 调用的 C++ 函数时,捕获所有内容。

【讨论】:

  • IME 在 C 中指示错误或失败的常用方法是返回一个被调用者忽略的值。 :(
  • @sbi:IME,有些人仍然认为“返回值”也是 C++、Java 和 C# 的方式...:(
【解决方案2】:

如果您想进行多级跳转,请查看setjmp()longjmp()。它们可以用作原始异常抛出。 setjmp() 函数设置返回位置,并返回状态值。 longjmp() 函数返回到返回位置,并提供状态值。您可以通过在setjmp() 之后调用来创建catch 函数,具体取决于状态值。

无论出于何种原因,都不要在 C++ 中使用它们。它们不进行堆栈展开或调用析构函数。

【讨论】:

    【解决方案3】:

    C 不支持异常处理。

    有关于解决此问题的一种方法here 的信息。这显示了简单的setjmp/longjmp 方法,但也提供了更复杂的替代方法,并进行了深入介绍。

    【讨论】:

    • 嗯,今天第三次投反对票。这个有什么特别的原因吗?我认为这看起来很有希望。
    • +1 因为否决票是错误的...问题的完美链接
    • @John Dibling - 不用说...会留意,也许只是糟糕的一天。
    【解决方案4】:

    由于 C++ 最初是作为 C 预处理器实现的,并且它具有 Try / Catch,因此您可以重做 Bjarne Stroustrup 的工作并编写一个预处理器来完成它。

    【讨论】:

    • 当异常被引入 C++ 时,C 预处理器的实现已经成为历史。
    • @Nemanja:原来的 cfront 确实如此。但是,Comeau C++ 仍然输出 C 代码。 (因为这带来了高可移植性。)但我怀疑这是否比任何其他编译器的汇编器输出更有用:它是机器生成的代码,因此不适合人类使用。
    • 我相信异常是压垮骆驼的最后一根稻草,而有问题的骆驼是 cfront C++ 编译器。参见 Stroupstrup “C++ 的设计与演变”。
    【解决方案5】:

    有经典的展开gotos 模式:

    FILE *if = fopen(...);
    FILE *of = NULL;
    if (if == NULL) return; 
    
    of = fopen(...);
    if (of == NULL) goto close_if;
    
    /* ...code... */
    if (something_is_wrong) goto close_of;
    
    /* ... other code... */
    
    close_of:
      fclose(of);
    close_if:
      fclose(if);
    
    return state;
    

    或者,您可以通过在另一个函数中隔离“try”代码来以有限的方式伪造它

    int try_code(type *var_we_must_write, othertype var_we_only_read /*, ... */){
      /* ...code... */
      if (!some_condition) return 1;
      /* ...code... */
      if (!another_condition) return 2;
      /* ...code... */
      if (last_way_to_fail) return 4;
      return 0;
    }
    
    void calling_routine(){
      /* ... */
      if (try_code(&x,y/*, other state */) ) {
         /* do your finally here */
      }
     /* ... */
    }
    

    但这两种方法都不是完全等效的。您必须自己管理所有资源,在找到处理程序之前不会自动回滚,等等......

    【讨论】:

    • 哎呀!这对我来说真的很乱。我更喜欢我在下面建议的方法。无需 goto!
    • goto 是 C 语言的必经之路(请原谅双关语)。不过,拥有多个退出标签似乎有点矫枉过正。使用单个退出标签并(如有必要)检查分配了哪些资源会更简洁(尽管效率稍低)。
    • @jamesdlin:多个标签给你买了一些特定的东西:每个标签对应一个可能需要释放的资源。您可以通过发布条件来避免它们,但在某些情况下(不是这个)需要额外的标志。据我所知,品味问题。
    • 是的,但它的代价是一些额外的复杂性,而且 IMO 维护起来有点困难(因为顺序很重要)。在大多数情况下,无论如何都不需要额外的标志(和额外的检查);如果在虚拟值(例如NULL)上调用清理函数(不是fclose),通常应该是无操作的。
    • @jamesdlin:当我完成这种清理方式(通常在汇编程序而不是 C 语言中)时,我发现 goto 版本更干净,更简单,因为潜在资源总是分配在相同的顺序。你可以很容易地知道goto 是哪一个,因为在代码中的任何给定点你都知道你有什么资源(我希望)。
    【解决方案6】:

    您可以在本书中找到使用 longjmp 的可能实现:C Interfaces and Implementations: Techniques for Creating Reusable Software - David Hanson

    您可以找到代码herehere作为书中使用的样式的示例:

    除了.h

    /* $Id$ */
    #ifndef EXCEPT_INCLUDED
    #define EXCEPT_INCLUDED
    #include <setjmp.h>
    #define T Except_T
    typedef struct T {
        const char *reason;
    } T;
    typedef struct Except_Frame Except_Frame;
    struct Except_Frame {
        Except_Frame *prev;
        jmp_buf env;
        const char *file;
        int line;
        const T *exception;
    };
    enum { Except_entered=0, Except_raised,
           Except_handled,   Except_finalized };
    extern Except_Frame *Except_stack;
    extern const Except_T Assert_Failed;
    void Except_raise(const T *e, const char *file,int line);
    #ifdef WIN32
    #include <windows.h>
    
    extern int Except_index;
    extern void Except_init(void);
    extern void Except_push(Except_Frame *fp);
    extern void Except_pop(void);
    #endif
    #ifdef WIN32
    /* $Id$ */
    #define RAISE(e) Except_raise(&(e), __FILE__, __LINE__)
    #define RERAISE Except_raise(Except_frame.exception, \
        Except_frame.file, Except_frame.line)
    #define RETURN switch (Except_pop(),0) default: return
    #define TRY do { \
        volatile int Except_flag; \
        Except_Frame Except_frame; \
        if (Except_index == -1) \
            Except_init(); \
        Except_push(&Except_frame);  \
        Except_flag = setjmp(Except_frame.env); \
        if (Except_flag == Except_entered) {
    #define EXCEPT(e) \
            if (Except_flag == Except_entered) Except_pop(); \
        } else if (Except_frame.exception == &(e)) { \
            Except_flag = Except_handled;
    #define ELSE \
            if (Except_flag == Except_entered) Except_pop(); \
        } else { \
            Except_flag = Except_handled;
    #define FINALLY \
            if (Except_flag == Except_entered) Except_pop(); \
        } { \
            if (Except_flag == Except_entered) \
                Except_flag = Except_finalized;
    #define END_TRY \
            if (Except_flag == Except_entered) Except_pop(); \
            } if (Except_flag == Except_raised) RERAISE; \
    } while (0)
    #else
    #define RAISE(e) Except_raise(&(e), __FILE__, __LINE__)
    #define RERAISE Except_raise(Except_frame.exception, \
        Except_frame.file, Except_frame.line)
    #define RETURN switch (Except_stack = Except_stack->prev,0) default: return
    #define TRY do { \
        volatile int Except_flag; \
        Except_Frame Except_frame; \
        Except_frame.prev = Except_stack; \
        Except_stack = &Except_frame;  \
        Except_flag = setjmp(Except_frame.env); \
        if (Except_flag == Except_entered) {
    #define EXCEPT(e) \
            if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
        } else if (Except_frame.exception == &(e)) { \
            Except_flag = Except_handled;
    #define ELSE \
            if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
        } else { \
            Except_flag = Except_handled;
    #define FINALLY \
            if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
        } { \
            if (Except_flag == Except_entered) \
                Except_flag = Except_finalized;
    #define END_TRY \
            if (Except_flag == Except_entered) Except_stack = Except_stack->prev; \
            } if (Except_flag == Except_raised) RERAISE; \
    } while (0)
    #endif
    #undef T
    #endif
    

    除了.c

    static char rcsid[] = "$Id$" "\n$Id$";
    #include <stdlib.h>
    #include <stdio.h>
    #include "assert.h"
    #include "except.h"
    #define T Except_T
    Except_Frame *Except_stack = NULL;
    void Except_raise(const T *e, const char *file,
        int line) {
    #ifdef WIN32
        Except_Frame *p;
    
        if (Except_index == -1)
            Except_init();
        p = TlsGetValue(Except_index);
    #else
        Except_Frame *p = Except_stack;
    #endif
        assert(e);
        if (p == NULL) {
            fprintf(stderr, "Uncaught exception");
            if (e->reason)
                fprintf(stderr, " %s", e->reason);
            else
                fprintf(stderr, " at 0x%p", e);
            if (file && line > 0)
                fprintf(stderr, " raised at %s:%d\n", file, line);
            fprintf(stderr, "aborting...\n");
            fflush(stderr);
            abort();
        }
        p->exception = e;
        p->file = file;
        p->line = line;
    #ifdef WIN32
        Except_pop();
    #else
        Except_stack = Except_stack->prev;
    #endif
        longjmp(p->env, Except_raised);
    }
    #ifdef WIN32
    _CRTIMP void __cdecl _assert(void *, void *, unsigned);
    #undef assert
    #define assert(e) ((e) || (_assert(#e, __FILE__, __LINE__), 0))
    
    int Except_index = -1;
    void Except_init(void) {
        BOOL cond;
    
        Except_index = TlsAlloc();
        assert(Except_index != TLS_OUT_OF_INDEXES);
        cond = TlsSetValue(Except_index, NULL);
        assert(cond == TRUE);
    }
    
    void Except_push(Except_Frame *fp) {
        BOOL cond;
    
        fp->prev = TlsGetValue(Except_index);
        cond = TlsSetValue(Except_index, fp);
        assert(cond == TRUE);
    }
    
    void Except_pop(void) {
        BOOL cond;
        Except_Frame *tos = TlsGetValue(Except_index);
    
        cond = TlsSetValue(Except_index, tos->prev);
        assert(cond == TRUE);
    }
    #endif
    

    【讨论】:

      【解决方案7】:

      我喜欢使用的一种有用的编码风格如下。我不知道它是否有一个特定的名称,但是当我将一些汇编代码逆向工程为等效的 C 代码时,我遇到了它。你确实失去了一个缩进级别,但这对我来说没什么大不了的。注意会指出无限循环的评论者! :)

      int SomeFunction() {
          int err = SUCCESS;
      
          do {
              err = DoSomethingThatMayFail();
              if (err != SUCCESS) {
                  printf("DoSomethingThatMayFail() failed with %d", err);
                  break;
              }
      
              err = DoSomethingElse();
              if (err != SUCCESS) {
                  printf("DoSomethingElse() failed with %d", err);
                  break;
              }
      
              // ... call as many functions as needed.
      
              // If execution gets there everything succeeded!
              return SUCCESS;
          while (false);
      
          // Something went wrong!
          // Close handles or free memory that may have been allocated successfully.
      
          return err;
      }
      

      【讨论】:

      • 不错。如果您需要有条件地展开内容,您可能需要嵌套实例。
      【解决方案8】:

      这是我用 C 语言实现的异常处理系统:exceptions4c

      它由宏提供支持,建立在 setjmplongjmp 之上,并且是 100% 可移植的 ANSI C。

      There你还可以找到我所知道的所有不同实现的列表。

      【讨论】:

        猜你喜欢
        • 2011-01-23
        • 2012-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-03
        • 2020-12-19
        • 1970-01-01
        • 2012-04-25
        相关资源
        最近更新 更多