try-catch 的实现

2024-06-01 12:04:09 浏览数 (4)

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 创建 的回调函数就可以。

1 人点赞