C语言中Try/Catch的实现
许多高级语言中都有Try/Catch的实现用于处理异常。例如在C++中
try {
// ... do something
// ... throw a exception
} catch (exception &e) {
// handled the specific exception
} catch (...) {
// handled other exception.
}
可以通过以上语句块来实现异常的捕获和处理。
C语言中并没有直接提供try/catch/throw的实现。但是C提供了两个跳转函数setjmp与longjmp可以实现
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
1. 以下例子,使用setjmp/longjmp将模拟Try/Catch的机制实现跳转
// try / catch -->setjmp/longjmp
#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
int count = 0;
void sub_func(int idx) {
printf("sub_func --> idx :%d\n", idx);
longjmp(env, idx); // throw idx
}
int main() {
int idx = 0;
count = setjmp(env);
if (count == 0) { // try
printf("count : %d\n", count);
sub_func(++idx);
} else if (count == 1) { // catch(1)
printf("count : %d\n", count);
sub_func(++idx);
} else if (count == 2) { // catch(2)
printf("count : %d\n", count);
sub_func(++idx);
} else if (count == 3) { // catch(3)
printf("count : %d\n", count);
sub_func(++idx);
} else { // catch(...)
printf("other count \n");
}
{ // finally
}
}
执行结果如下:
2. 可以将setjmp与longjmp的语句块定义成宏。这样可以写成类似以下:
// try / catch -->setjmp/longjmp
#include <stdio.h>
#include <setjmp.h>
typedef struct _Exception {
jmp_buf env;
int exceptype;
} Exception;
#define Try(excep) if ((excep.exceptype = setjmp(excep.env)) == 0)
#define Catch(excep, ExcepType) else if (excep.exceptype == ExcepType)
#define Throw(excep, ExcepType) longjmp(excep.env, ExcepType)
#define Finally
void throw_func(Exception ex, int idx) {
printf("sub_func --> idx :%d\n", idx);
Throw(ex, idx);
}
int main() {
int idx = 0;
Exception ex;
Try(ex) {
printf("count : %d\n", idx);
throw_func(ex, ++idx);
} Catch(ex, 1) {
printf("count : %d\n", ex.exceptype);
} Catch(ex, 2) {
printf("count : %d\n", ex.exceptype);
} Catch(ex, 3) {
printf("count : %d\n", ex.exceptype);
} Finally {
printf("Finally\n");
}
}
执行结果如下:
3. 如果是多线程或者多层嵌套的情况怎么办?
1)可以看到在上面的代码中,将异常定义的“帧”数据结构,然后通过函数传入栈内的方法会增加编码复杂度和代码的耦合性,这种方法不可取。有什么办法解决呢?
可以将异常帧的数据结构定义为全局。
2)多层嵌套抛出异常的情况要怎么办?
可以将每一层的异常帧传入一个定义在全局的栈数据结构中,每进入一层“Try”语句块,就创建一个新的“帧”。后续代码每抛出一个异常,就可以在全局的异常帧中pop出栈顶的帧。
每个帧之间才用链表的方式来存储。这样就达到了不需要在所有内层嵌套函数中传递栈帧的操作。
3)多线程怎么办呢?
可以利用posix中的pthread_key_t数据结构,定义每个线程的局部存储。然后利用创建,设置与获取线程局部存储数据。
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
int pthread_setspecific(pthread_key_t key,const void *pointer));
void *pthread_getspecific(pthread_key_t key);
基本使用方法是
使用 pthread_key_t 创建线程局部key并用pthread_key_create对齐初始化
后续每个线程通过pthread_setspecific与pthread_getspecific分别来设置与获取线程局部变量。
完整的测试代码如下:
// try / catch -->setjmp/longjmp
#include <stdio.h>
#include <setjmp.h>
#include <stdarg.h>
#include <string.h>
#include <pthread.h>
#define ThreadLocalData pthread_key_t
#define ThreadLocalDataSet(key, value) pthread_setspecific((key), (value))
#define ThreadLocalDataGet(key) pthread_getspecific((key))
#define ThreadLocalDataCreate(key) pthread_key_create(&(key), NULL)
#define EXCEPTION_MESSAGE_LENGTH 512
ThreadLocalData ExceptionStack;
// Exception Flags
enum {
ExceptionEntered = 0,
ExceptionThrown,
ExceptionHandled,
ExceptionFinalized
};
// Exception Type
typedef enum {
ExceptionTypeA = 0,
ExceptionTypeB,
ExceptionTypeC,
ExceptionTypeD
} ExceptionType;
char * exceptionTable[] = {
"ExceptionTypeA",
"ExceptionTypeB",
"ExceptionTypeC",
"ExceptionTypeD"
};
typedef struct _Exception {
jmp_buf env;
int line;
const char * func;
const char * file;
ExceptionType exceptype;
struct _Exception * prev;
char msg[EXCEPTION_MESSAGE_LENGTH + 1];
} Exception;
#define ExceptionPopStack() \
ThreadLocalDataSet(ExceptionStack, ((Exception*) ThreadLocalDataGet(ExceptionStack))->prev)
#define ReThrow() ExceptionThrow(excep.exceptype, excep.func, excep.file, excep.line, NULL)
#define Throw(e, cause, ...) ExceptionThrow((e), __func__, __FILE__, __LINE__, cause, ##__VA_ARGS__, NULL)
#define Try do { \
Exception excep; \
excep.msg[0] = '\0'; \
excep.prev = (Exception*)ThreadLocalDataGet(ExceptionStack); \
ThreadLocalDataSet(ExceptionStack, &excep); \
int Exception_flag = setjmp(excep.env); \
if (Exception_flag == ExceptionEntered) { //}
#define Catch(e) \
if (Exception_flag == ExceptionEntered) ExceptionPopStack(); \
} else if (excep.exceptype == (e)) { \
Exception_flag = ExceptionHandled;
#define Finally \
if (Exception_flag == ExceptionEntered) ExceptionPopStack(); \
} { \
if (Exception_flag == ExceptionEntered) \
Exception_flag = ExceptionFinalized;
#define EndTry \
if (Exception_flag == ExceptionEntered) ExceptionPopStack(); \
} if (Exception_flag == ExceptionThrown) ReThrow(); \
} while (0)
//
void ExceptionThrow(ExceptionType e, const char * func, const char * file, int line, const char * cause, ...) {
Exception * excep = (Exception *)ThreadLocalDataGet(ExceptionStack);
va_list ap;
if (excep) {
va_start(ap, cause);
vsnprintf(excep->msg, EXCEPTION_MESSAGE_LENGTH, cause, ap);
va_end(ap);
excep->exceptype = e;
excep->func = func;
excep->file = file;
excep->line = line;
ExceptionPopStack();
longjmp(excep->env, ExceptionThrown);
} else {
char message[EXCEPTION_MESSAGE_LENGTH+1];
va_start(ap, cause);
vsnprintf(message, EXCEPTION_MESSAGE_LENGTH, cause, ap);
va_end(ap);
printf("Unhandled %s: %s\n raised in %s at %s:%d\n",exceptionTable[e], message, func ? func : "?", file ? file : "?", line);
}
}
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static void init_once(void) {
ThreadLocalDataCreate(ExceptionStack);
}
void ExceptionInit(void) {
pthread_once(&once_control, init_once);
}
#define THREADS 50
void *thread_func(void * args) {
pthread_t selfid = pthread_self();
// test different type exception with try catch.
Try {
Throw(ExceptionTypeA, "A");
} Catch(ExceptionTypeA) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeA], selfid);
} EndTry;
Try {
Throw(ExceptionTypeB, "B");
} Catch(ExceptionTypeB) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeB], selfid);
} EndTry;
Try {
Throw(ExceptionTypeC, "C");
} Catch(ExceptionTypeC) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeC], selfid);
} EndTry;
Try {
Throw(ExceptionTypeD, "D");
} Catch(ExceptionTypeD) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeD], selfid);
} EndTry;
// test the control flow of try catch.
Try {
Throw(ExceptionTypeA, "A Again");
Throw(ExceptionTypeB, "B Again");
Throw(ExceptionTypeC, "C Again");
Throw(ExceptionTypeD, "D Again");
} Catch(ExceptionTypeA) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeA], selfid);
} Catch(ExceptionTypeB) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeB], selfid);
} Catch(ExceptionTypeC) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeC], selfid);
} EndTry;
// test unhandled exception.
Try {
Throw(ExceptionTypeD, "D");
} Catch(ExceptionTypeA) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeA], selfid);
} Catch(ExceptionTypeB) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeB], selfid);
} Catch(ExceptionTypeC) {
printf("Handled %s! %ld\n", exceptionTable[ExceptionTypeC], selfid);
} EndTry;
}
int main() {
ExceptionInit();
printf("test single-thread exception!\n");
thread_func(NULL);
printf("test multi-thread exception!\n");
int i = 0;
pthread_t threads[THREADS];
for(i = 0; i < THREADS; i++)
pthread_create(&threads[i], NULL, thread_func, NULL);
for(i = 0; i < THREADS; i++)
pthread_join(threads[i], NULL);
printf("test finished!\n");
return 0;
}
测试结果如下: