try-catch
基本所有的编程语言都会有异常捕捉的语法,try-catch 基本是所有编程语言都会有的信息,他会捕捉 try 中语法错误,如果存在语法错误就会执行 catch 的内容。
在上代码之前,我们首先需要确定,如果我们自己实现一个 try-catch 我们需要的是什么?
首先我们代码执行在我们看来是一行代码一行代码执行,操作系统看来就是一个线程,一个进程的执行,所以发生错误的时候,除了执行 catch 信息,我们需要让操作系统对线程或者进行进行操作。
怎么做呢?如果进行过代码调试,我们都会看到在代码执行到断点的时候发现,IDE 会返回进行堆栈调用以及各个变量的值,当然这个在我们的编程语言中有一个专门的术语叫做上下文信息。
线程数据键
在 c 语言中,我们对于线程操作除了多线程创建线程,设置回调函数,还可以对线程进行设置,例如线程数据键的设置。
代码语言:c复制/*
线程数据键是用来标识线程特定数据的访问和管理,他可以让每个线程都有自己的数据副本不会相互干扰
可以使用在线程隔离,数据独立,简化代码
使用1、pthread_key_create 创建数据键
2、pthread_setspecific 设置特定数据
3、pthread_getspecific 获取特定数据
数据键的数据类型是 pthread_key_t
内核层面:
内核层面依赖于 TLS (线程局部存储)机制
内核关键实现:
1、线程控制块 TCB 内核存储线程的所有信息
2、TLS 机制用于独立内存访问
3、硬件支持相关访问机制
用户可以通过 arch_prctl 进行系统调用,设置 TLS 基地址
使用 ARCH_SET_FS ARCH_SET_GS 设置 fs 或者 gs 基地址
*/
#include <pthread.h>
#define ntyThreadData pthread_key_t //pthread_key_t 是用于标识线程特定数据键的数据类型。
#define ntyThreadDataSet(key,value) pthread_setspecific((key), (value)) //pthread_setspecific 函数用于将特定的数据值与线程特定数据键相关联。
#define ntyThreadDataGet(key) pthread_getspecific((key)) //pthread_getspecific 函数用于获取与线程特定数据键相关联的值。
#define ntyThreadDataCreate(key) pthread_key_create(&(key), NULL) //pthread_key_create 函数用于创建一个新的线程特定数据键
上述大概对于数据键信息进行了描述,简单来说就是对线程进行标记,然后希望线程之间进行隔离的机制。
上下文信息保存
c 语言使用了jmp_buf 是 c 语言中用于实现非本地跳转,setjmp 函数可以用于保存调用环境信息
longjmp 函数可以回复保存的调用环境
setjmp 和 longjmp 函数
setjmp 函数:
int setjmp(jmp_buf env);
该函数保存当前的调用环境到 env 中,并返回 0。当通过 longjmp 跳转到此环境时,setjmp 返回一个非零值。
longjmp 函数:
void longjmp(jmp_buf env, int val);
该函数恢复 env 保存的调用环境,并导致 setjmp 返回 val。如果 val 为 0,setjmp 返回 1。
这两个函数都保存在头文件#include <setjmp.h> 中。
代码实现
有了上述的背景铺垫,那么 try-catch 实现相对容易理解一点。
定义数据结构
代码语言:c复制//对不同异常类型定义
typedef struct _ntyException{
const char* name;
} ntyException;
ntyException SQLException = {"SQLException"};
ntyException TimeoutException = {"TimeoutException"};
//声明了一个pthread_key_t 的线程数据键的类型
ntyThreadData ExceptionStack;
typedef struct _ntyExceptionFrame{
jmp_buf env; //用于保存环境调用信息
int line;
const char* func; //回调函数
const char* file;
ntyException *exception; //异常的名字
struct _ntyException *prev; //上一个异常的指针
char message[ EXCEPTION_MESSAGE_LENGTH 1]; //异常信息
}ntyExceptionFrame;
上述内容应该较为简单,就是设置异常的基本信息,回调函数加上异常本身的信息。
部分宏定义:
代码语言:c复制#define ntyExceptionPopStack
ntyThreadDataSet(ExceptionStack, (ntyExceptionFrame*)ntyThreadDataGet(ExceptionStack)->prev) //将上一个异常的的值设置在 pthread_key_t 上
#define ReThrow ntyExceptionThrow(frame.exception, frame.func, frame.file, frame.line, NULL) //抛出有上下文的异常
#define Throw(e, cause, ...) ntyExceptionThrow(&(e), __func__, __FILE__, __LINE__, cause, ##__VA_ARGS__, NULL) //多种类型异常抛出
enum { //枚举异常
ExceptionEntered = 0,
ExceptionThrown,
ExceptionHandled,
ExceptionFinalized
};
定义异常抛出的函数。
代码语言:c复制void ntyExceptionThrow(ntyException* excep, const char* func, const char*file, int line, const char*cause, ...){
va_list ap; //不定参数的list
ntyExceptionFrame *frame = (ntyExceptionFrame*)ntyThreadDataGet(ExceptionStack); //获取特定线程数据值
if(frame){
frame->exception = excep; //将要抛出的各种参数设置在 frame 上
frame->func = func;
frame->file = file;
frame->line = line;
if(cause){ //有错误原因
va_start(ap, cause);
vsnprintf(frame->message, EXCEPTION_MESSAGE_LENGTH, cause, ap); //输出
va_end(ap);
}
ntyExceptionPopStack; //将异常出栈
longjmp(frame->env, ExceptionThrown); //恢复上下文
}else if(cause){ //各种 frame 信息不存在,但是有错误信息
char message[EXCEPTION_MESSAGE_LENGTH 1]; //只输出错误信息
va_start(ap, cause);
vsnprintf(message, EXCEPTION_MESSAGE_LENGTH, cause, ap);
va_end(ap);
printf("%s: %sn raised in %s at %s:%dn", excep->name, message, func ? func : "?", file ? file : "?", line);
} else { //直接数据错误相关信息
printf("%s: %pn raised in %s at %s:%dn", excep->name, excep, func ? func : "?", file ? file : "?", line);
}
}
抛出异常已经实现,但是初始化这些内容也可以。
代码语言:c复制static pthread_once_t once_control = PTHREAD_ONCE_INIT;
//创建数据键
static void init_once(void){
ntyThreadDataCreate(ExceptionStack);
}
//异常输出啊
static ntyExceptionInit(void){
pthread_once(&once_control, init_once);
}
我们母亲把异常抛出所需要的机制定义好了,但是真正的机制实现我们需要是心啊 try 和 catch 能够使用这个逻辑,而 C 语言中,只有宏定义才能引入一个外部变量,所以宏定义如下:
代码语言:c复制#define Try do{
volatile int Exception_flag; //不用优化定义一个int flag
ntyExceptionFrame frame;
frame.message[0] = 0;
frame.prev = (ntyExceptionFrame*)ntyThreadDataGet (ExceptionStack); //将frame 上一个指向为获取数据值
ntyThreadDataSet(ExceptionStack, &frame);
Exception_flag = setjmp(frame.env); //保存上下文
if(Exception_flag == ExceptionEntered){
#define Catch(e) //发生错误
if(Exception_flag == ExceptionEntered) ntyExceptionPopStack;
}else if(frame.exception == &(e)){
Exception_flag = ExceptionHandled;
#define Finally
if(Exception_flag == ExceptionEntered) ntyExceptionPopStack;
}{ //如果不是以上错误进入这个环节
if(Exception_flag == ExceptionEntered)
Exception_flag = ExceptionFinalized;
#define EndTry //结束。
if(Exception_flag == ExceptionEntered) ntyExceptionPopStack;
} if (Exception_flag == ExceptionThrown) ReThrow;
} while (0)
最后就是这个使用。
代码语言:c复制ntyException A = {"AException"};
ntyException B = {"BException"};
ntyException C = {"CException"};
ntyException D = {"DException"};
void *thread(void *args){
pthread_t selfid = pthread_self();
Try{
Throw(A, "A");
} Catch(A){
printf("catch A :%ldn", selfid);
} EndTry;
Try {
Throw(C, "C");
} Catch (C) {
printf("catch C : %ldn", selfid);
} EndTry;
Try {
Throw(D, "D");
} Catch (D) {
printf("catch D : %ldn", selfid);
} EndTry;
Try {
Throw(A, "A Again");
Throw(B, "B Again");
Throw(C, "C Again");
Throw(D, "D Again");
} Catch (A) {
printf("catch A again : %ldn", selfid);
} Catch (B) {
printf("catch B again : %ldn", selfid);
} Catch (C) {
printf("catch C again : %ldn", selfid);
} Catch (D) {
printf("catch B again : %ldn", selfid);
} EndTry;
}
如果要实验测试,将该函数中内容设置为 pthread 创建 的回调函数就可以。