c语言sprintf_C语言模拟异常

d65688c1208457d9e25f8739cb5b16d5.png
实用性不大,可以当做熟悉调用栈的一个手段。

你写的代码当然不会有bug,但是有时候你实在太困了,写了一个一除以零,程序瞬间崩了。程序中难免会有异常,可预知的不可预知的都在时刻发生着,这些异常往往会导致程序运行终止。如果在程序中加上各种各样的判断,不仅会使代码变得臃肿、可读性低,而且总有你预想不到的情况。为了解决这种问题,一些编程语言中引入了异常处理机制,可以在发生异常的情况下不中断程序的运行,。然而,C语言作为一种古老的语言,尽管时刻散发着成熟的魅力,但却总显得有些落伍,本文将会为C语言量身订造一身潮牌,尽管这身潮牌比较省“布料“。

异常有三个主要的关键字,trycatchthrow。try和catche必须搭配使用,try关键字标识代码块运行受保护,出现异常时会立刻终止转向执行catche代码块,否则将跳过catche代码块,继续执行后面的代码,throw用于抛出异常。C语言中是没有这些功能的,只能简单的去模拟,所以说比较省“布料”。

1. 函数调用栈

栈是一种后进先出(LIFO)的数据结构,程序运行其实就是按照一定的逻辑调用不同的函数,在函数调用时,系统会在内存中维护一个“调用记录“,以栈的形式存储,称为调用栈,每一个函数调用称为调用帧,调用函数时会将一个新的调用帧压入栈,调用完毕后会将调用帧出栈,这样在程序启动时调用栈为空,正常结束时调用栈恢复为空,如果运行过程中出现异常导致程序终止,此时调用栈并未清空,所以可以通过调用栈判断那个函数出现了问题。C语言并没有提供调用栈相关的接口(才疏学浅可能没有找到,欢迎评论指正),所以只能动手写了一个,先定一个调用帧的结构体:

// runTimeStack.h

小啰嗦一句,上面这个结构体起了两个名字,我在读标准库的源码时经常看到这种写法,语义可以表达的很清楚。调用栈使用单向链表实现,除了链指针外只有一个属性message,用来存储当前栈的一些信息。一个线程应该只有一个调用栈,为了保证唯一性需要进行一点简单的封装:

// runTimeStack.cpp

其它文件引入runTimeStack.h时stack变量不会被暴露出去,外界可以通过getRunTimeStack()获取,这样就可以保证所有地方获取的都是一个stack,接下来是两个至关重要的宏,也是实现收集调用栈的核心(思考一下为什么不可以用函数?):

#define IN_STACK do { 

要理解这两个宏,先要了解这三个标准宏(前后都是两个下划线)__func____FILE____LINE__,其实不用“谈宏色变“,理解了宏是什么以后特别简单,一言蔽之就是简单的文本替换,而有一些标准宏完全不用理会它的原理,就像上面这三个,只要知道怎么用就可以了,__func__可以得到当前所在函数的名称,把它放在main中,它的值就是main,放在test函数中它的值就是test,__FILE__可以得到当前所在文件的绝对路径,__LINE__就更简单了,表示当前在文件的哪一行。所以可以通过这三个值定位到一个精准的位置。

IN_STACK会生成当前位置的调用帧写入全局栈,OUT_STACK则会吧全局栈的栈顶移除,涉及到Error的代码可以先不用看,后文会提到。回到之前提到过的问题,IN_STACK和OUT_STACk可以用函数来代替吗?答案是不可以的,使用到的三个标准宏有一个共同的特点,它们都与自身所在位置紧密相关,如果放在一个函数中,那得到的结果都是这个函数的位置,而宏定义就是简单的文本替换,最终的代码会被放到宏的位置,那这三个标准宏得到的自然就是正确的结果了。

// runTimeStack.cpp

写一个简单的例子测试一下:

#include

运行结果如下:

test3............
test2............
test1............
    at test1 (F:VS输出目录asyncmain.cpp: 11)
    at test2 (F:VS输出目录asyncmain.cpp: 18)
    at test3 (F:VS输出目录asyncmain.cpp: 25)
    at main (F:VS输出目录asyncmain.cpp: 42)

效果还不错,有了调用栈以后就可以做很多事了。

2. 异常的捕获与处理

二话不说先上定义:

typedef 

message属性表示异常的信息,stack属性是异常的调用栈信息,此处的异常栈和调用栈在没有发生异常的情况是一致的,当发生异常的时候异常栈还要记录一些异常信息,举个简单例子说明,函数A被调用(入调用栈),函数A内部抛出异常,函数A终止执行(出调用栈),返回上一层,此时捕获异常时,由于函数A已经出栈,无法获取到异常信息,所以还要维护一个异常栈。做好这些准备以后,真正的主角开始登场:

#define Try do { Error __error__ = initError();

先不着急解释代码的意思,先来看一个简单的例子:

#include

运行结果如下:

test3............
test2............
test1............
这里发生异常了!!!
    at test1 (F:VS输出目录asyncmain.cpp: 7)
    at test1 (F:VS输出目录asyncmain.cpp: 5)
    at test2 (F:VS输出目录asyncmain.cpp: 12)
    at test3 (F:VS输出目录asyncmain.cpp: 19)
    at main (F:VS输出目录asyncmain.cpp: 26)

把函数中的宏翻译一下:

void 

现在看来是不是很简单,所谓的Try-Catch就是一个break中断,同上文提到过的全局调用栈stack一样,也创建了一个被封装的全局Error,只不过初始值为NULL,只有在Try的时候会进行初始化:

Error 

初始化Error的时候要把当前调用栈拷贝到Error中,message赋值为NULL(判断有无异常的标识),Catch则判断error的message属性有没有被赋值,不为NULL时说明发生了异常,则要执行后面处理异常的代码。CatchEnd负责回收Error的内存,并重新复制为NULL。还有一点需要提到,Throw是可以单独使用的宏,它会先获取全局Error,如果为空的先初始化,然后写入异常调用帧。在IN_STACK中也有一部分涉及到了Error的处理,如果Error不为NULL,则要将调用帧同步写入Error中。

完整的实现:源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UDP重传是指当UDP数据包在传输过程中丢失或损坏时,需要通过重新发送数据包来进行补发。下面是一个简单的UDP重传的C语言实现代码示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #define MAX_BUF_SIZE 1024 #define SERVER_PORT 8888 int main() { int sockfd; struct sockaddr_in serverAddr; socklen_t serverAddrLen = sizeof(serverAddr); char recvBuf[MAX_BUF_SIZE]; char sendBuf[MAX_BUF_SIZE]; // 创建UDP socket if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } // 设置服务器地址 memset(&serverAddr, 0, serverAddrLen); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(SERVER_PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; while (1) { // 发送数据包 sprintf(sendBuf, "Hello, UDP!"); sendto(sockfd, sendBuf, strlen(sendBuf), 0, (struct sockaddr*)&serverAddr, serverAddrLen); // 接收数据包 int recvLen = recvfrom(sockfd, recvBuf, MAX_BUF_SIZE, 0, (struct sockaddr*)&serverAddr, &serverAddrLen); if (recvLen < 0) { // 重传数据包 printf("Resending data packet\n"); continue; } recvBuf[recvLen] = '\0'; printf("Received: %s\n", recvBuf); // 模拟处理过程 sleep(1); } close(sockfd); return 0; } ``` 以上代码通过socket建立了UDP连接,并通过发送和接收数据包来实现基本的UDP通信。如果接收失败,就会进行数据包的重传。这样就实现了简单的UDP重传机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值