25

我有一些我正在使用的 C 代码,并且在代码运行时发现错误,但是关于如何进行正确的 try/catch 的信息很少(如在 C# 或 C++ 中)。

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

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

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

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

然而,我的主要问题只是如何在 ANSI C 中进行尝试/捕获……实现/示例不必是递归的。

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();
}
4

8 回答 8

26

一般来说,你不会。

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

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

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

于 2010-09-21T17:03:23.167 回答
17

C 不支持异常处理。

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

于 2010-09-21T17:04:36.703 回答
4

我喜欢使用的一种有用的编码风格如下。我不知道它是否有一个特定的名称,但是当我将一些汇编代码逆向工程为等效的 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;
}
于 2010-09-21T19:26:57.690 回答
4

这是我在 C: exceptions4c中实现的异常处理系统。

它由宏驱动,建立在 ANSI C 之上,setjmp并且longjmp是 100% 可移植的 ANSI C。

在那里,您还可以找到我所知道的所有不同实现的列表。

于 2011-04-28T07:05:50.273 回答
4

有经典的 unwinding 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;

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

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 */
  }
 /* ... */
}

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

于 2010-09-21T18:51:22.423 回答
3

您可以在本书中找到使用 longjmp 的可能实现:C 接口和实现:创建可重用软件的技术 - David Hanson

您可以在此处此处找到代码,作为书中使用的样式的示例:

除了.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
于 2010-09-21T19:00:29.887 回答
1

如果您想进行多级跳跃,请查看setjmp()longjmp()。它们可以用作原始异常抛出。该setjmp()函数设置一个返回位置,并返回一个状态值。该longjmp()函数转到返回位置,并提供状态值。您可以根据状态值catch通过调用来创建函数。setjmp()

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

于 2010-09-21T17:03:53.277 回答
0

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

于 2010-09-21T17:16:31.423 回答