引言

这是关于C中如何使用异常机制的讨论.顺带讲一讲C中魔法函数的setjmp内部机制.
通过它实现高级的异常try...catch. 允许我先扯一段面试题. 对于计算机面试题. 算法题等.觉得还是有意义的.

在你封装基础库的时候会简单用的.因为大家都会得你也会那是及格.如果你想及格+1的话...

开始吧,题目是这样的

/*
* 面试问题 假如有数组 int a[] = {1,2,3,4,5,6,7}, n = 7表示长度, k = 2表示移动步长
* 移动结果变成 1234567=> 6712345.
* 同样假如 k=3移动三步 1234567=> 5671234
* 实现一个移动函数
* void convert(int a[], int n, int k);
*/

的时候基本基本说算法. 这里主要想通过这个问题引导异常机制上来.直接贴代码

// 数组移动函数
void
convert(int a[], int n, int k) {
int t=, i, j;// t是为了数据记录终止条件的
// 前面两个是没有移动的必要, 后面是已经整除形成周期了也不需要移动
if ((!a) || (n <= ) || !(k %= n)) return;
// 计算最优的k和n
k = k < ? k + n : k; // 开始真的移动了, 首先确定趟数, 一般趟就够了,特殊的就是能够整除的需要多趟 for (i = ; t<n && i < k; ++i) {
//开始循环了, 结束条件式循环到头了
for (j = (i + k) % n; j != i; j = (j + k) % n) {
++t;
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
}

关于上面

    // 前面两个是没有移动的必要, 后面是已经整除形成周期了也不需要移动
if ((!a) || (n <= ) || !(k %= n)) return;

就是C中最简单的异常机制听过事先 if判断条件达到异常应对作用. 应该是最简单的异常处理, 也是常用的一种方式.

附录完整的测试demo.c

#include <stdio.h>
#include <stdlib.h> // 数组打印函数
void aprint(int a[], int n);
// 数组移动函数
void convert(int a[], int n, int k); //添加一个数组打印宏, a只能是数组
#define ALEN(a) \
sizeof(a)/sizeof(*a)
#define APRINT(a) \
aprint(a, ALEN(a)) /*
* 面试问题 假如有数组 int a[] = {1,2,3,4,5,6,7}, n = 7表示长度, k = 2表示移动步长
* 移动结果变成 1234567=> 6712345.
* 同样假如 k=3移动三步 1234567=> 5671234
* 实现一个移动函数
* void convert(int a[], int n, int k);
*/
int main(int argc, char* argv[]) {
// 实现主体逻辑
int a[] = {, , , , , , , , }; APRINT(a);
convert(a, ALEN(a), );
APRINT(a);
convert(a, ALEN(a), -);
APRINT(a); return system("pause");
} // 数组打印函数
void
aprint(int a[], int n) {
int i = -;
printf("now data: ");
while (++i < n)
printf("%d", a[i]);
putchar('\n');
} // 数组移动函数
void
convert(int a[], int n, int k) {
int t=, i, j;// t是为了数据记录终止条件的
// 前面两个是没有移动的必要, 后面是已经整除形成周期了也不需要移动
if ((!a) || (n <= ) || !(k %= n)) return;
// 计算最优的k和n
k = k < ? k + n : k; // 开始真的移动了, 首先确定趟数, 一般趟就够了,特殊的就是能够整除的需要多趟 for (i = ; t<n && i < k; ++i) {
//开始循环了, 结束条件式循环到头了
for (j = (i + k) % n; j != i; j = (j + k) % n) {
++t;
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
}

大家是不是觉得很简单. 这就是异常的本质, 分支.

前言

  到这里会再说另一个和if很像如下处理

最后加上default对于所有情况都处理好.也是异常处理的一种手段.

C开发中或者一个程序接口设计中一种很古老的一种设计有异常判断接口方法通过return 方法处理.

假如我们 需要写个函数返回两个数相除的函数.

#define _RT_OK        (0)                //结果正确的返回宏
#define _RT_EB (-1) //错误基类型,所有错误都可用它,在不清楚的情况下
#define _RT_EP (-2) //参数错误
#define _RT_EM (-3) //内存分配错误
#define _RT_EC (-4) //文件已经读取完毕或表示链接关闭
#define _RT_EF (-5) //文件打开失败 int
div(double a, double b, double *pc){
if(b== || !pa) return _RT_EP;
*pc = a / b;
return _RT_OK;
}

上面这种思路是一种复古套路也许只能C接口封装能够看见. 特别实诚. 这个技巧值得拥有.

返回函数运行的状态码, 通过指针返回最终结果值. 再扯一点.上面接口设计有一个小瑕疵,调用的时候

需要大量的if 判断. 假如是后端开发. 对于非法请求直接fake. (exit) 不用给前端返回状态码. 降低带宽.

好了到这里基本上, 简单开发中异常处理方式简单都介绍完了. 后面会实现 try ... catch机制.

正文

  到这里我们先看C中异常处理的魔法函数. 一个比goto更跳跃的函数. 支持函数之间跳跃.首先看一种实现的函数声明

// Function prototypes
int __cdecl setjmp(
_Out_ jmp_buf _Buf
);
    __declspec(noreturn) void __cdecl longjmp(
_In_ jmp_buf _Buf,
_In_ int _Value
);

上面看不明白的关键字(VS上的)直接忽略, 第一个函数setjmp 设置标志.第一次使用返回0.后面到这里来了 返回的是longjmp 第二个参数设置的值.

这里有个未定义现象. 就是千万不要用 longjmp 返回0 测试代码

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h> // 这里测试基础代码 longjmp(jbf, 0)
int main(void) {
volatile a = ;
jmp_buf jbf;
int rt; rt = setjmp(jbf);
if (rt == ) {
printf("a %d => %d\n", ++a, rt);
if (a == )
exit();
}
else {
printf("b %d => %d\n", ++a, rt);
} // 简单跳跃一下
if (a == )
longjmp(jbf, ); return system("pause");
}

运行结果就是未定义按照平台而定了.看下面

.

还有一个 jmp_buf 结构

    #define _JBLEN  16
#define _JBTYPE int typedef struct __JUMP_BUFFER
{
unsigned long Ebp;
unsigned long Ebx;
unsigned long Edi;
unsigned long Esi;
unsigned long Esp;
unsigned long Eip;
unsigned long Registration;
unsigned long TryLevel;
unsigned long Cookie;
unsigned long UnwindFunc;
unsigned long UnwindData[];
} _JUMP_BUFFER;

主要是保存当前寄存器信息让其longjmp的时候能够跳转. 这方面需要汇编知识. 本人不会. 有机会再学.

希望到这里关于 C的基础 setjmp longjmp api说清楚了. 下面看一个完整的 处理异常的案例

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h> // 带异常机制的触发运算
double jmpdiv(jmp_buf jbf, double a, double b); // 这里设置异常机制
int main(int argc, char* argv[]){
jmp_buf jbf;
double a = 2.0, b = , c;
int i = ;
// 随机数
while(++i <= ) {
printf("%d => ", i);
// 第一次调用setjmp 返回的值就为0
switch(setjmp(jbf)){
case :
b = rand() % ;
c = jmpdiv(jbf, a, b);
// try部分
printf("%lf / %lf = %lf\n", a,b,c);
break;
case -:
// 相当于catch 部分
fprintf(stderr, "throw new div is zero!\n");
break;
default:
fprintf(stderr, "uncat error!");
}
}
return ;
} double
jmpdiv(jmp_buf jbf, double a, double b) {
// 抛出异常
if(b == )
longjmp(jbf, -);
return a/b;
}

不好意思,今天 说的有点水. 讲的不好. 毕竟就是2个api. 用法也很固定. 主要C开发用在协程设计上. 异常处理基本if else switch goto都能解决了.

下面列举一个 关于 try ... catch 封装成宏的用法

#include <setjmp.h>

/* try 开始操作 */
#define TRYBEGIN(var) {\
jmp_buf var;\
switch (setjmp(var)){\
case : /* catch检测错误值 */
#define CATCH(z) \
break;\
case z: /* 默认捕捉的错误信息. 推荐在CATCH最后,如果CATCH存在的话 */
#define CATCHBASE() \
default: /* try最后结束标志 */
#define TRYEND \
break;\
}\
} /* throw 抛出异常 */
#define THROW(var, z) \
longjmp(var, z)

简单实用如下

    TRYBEGIN(jbf) {
puts("第一次运行到这个");
}
CATCH() { }
CATCHBASE() { }
TRYEND

到这里. 分享一个简易的通过setjmp 和 longjmp 实现协程案例

#include <stdio.h>
#include <setjmp.h> // 函数栈之间跳转用的变量
jmp_buf _mtask, _ctask; // 声明测试接口
void cushion(void);
void child(void); int i = ; // 主逻辑 执行测试
int main(void) {
if(!setjmp(_mtask))
cushion();
for(i=;i<;++i) {
puts("Parent");
if(!setjmp(_mtask))
longjmp(_ctask, );
}
} // 声明测试接口
void
cushion(void) {
char space[];
space[] = ;
child();
} void
child(void) {
for(i=;i<;++i) {
puts("Child loop begin");
// 第一次使用setjmp 函数返回0
if(!setjmp(_ctask))
longjmp(_mtask, ); puts("Child loop end");
if(!setjmp(_ctask))
longjmp(_mtask, );
}
}

我这里让其运行几次之后就直接结束了. 没上运行截图了. 对于setjmp 坑还是很多的. 栈区不够, 变量状态. 推荐自己

用几次感受一下. 不需要深究.只需要知道通过 setjmp + longjmp 能够实现异常机制try catch就可以了. 再扯一点对于finally实现难

一点点. 大家可以结合上面协程. 思考一下也可以实现的.

到这里 基本上就关于 异常机制的本质就结束了. 分支 + 跳转  

后记

  错误是难免的, 交流修改.互相提高认识. setjmp.h 的深入可以看

setjmp的正确使用   http://blog.codingnow.com/2010/05/setjmp.html

以后有机会还是分享一些实际案例开发吧. 如何设计,如何性能优化.拜拜~~~

C 几种异常机制简单讲述的更多相关文章

  1. Java异常处理机制及两种异常的区别

    java异常处理机制主要依赖于try,catch,finally,throw,throws五个关键字.   try 关键字后紧跟一个花括号括起来的代码块,简称try块.同理:下面的也被称为相应的块. ...

  2. C++ 异常机制分析(C++标准库定义了12种异常,很多大公司的C++编码规范也是明确禁止使用异常的,如google、Qt)

    阅读目录 C++异常机制概述 throw 关键字 异常对象 catch 关键字 栈展开.RAII 异常机制与构造函数 异常机制与析构函数 noexcept修饰符与noexcept操作符 异常处理的性能 ...

  3. C++异常机制的实现方式和开销分析 (大图,编译器会为每个函数增加EHDL结构,组成一个单向链表,非常著名的“内存访问违例”出错对话框就是该机制的一种体现)

    白杨 http://baiy.cn 在我几年前开始写<C++编码规范与指导>一文时,就已经规划着要加入这样一篇讨论 C++ 异常机制的文章了.没想到时隔几年以后才有机会把这个尾巴补完 :- ...

  4. C++ Primer笔记2_四种类型转换_异常机制

    1.类型转换 命名的强制类型转换: 有static_cast.dynamic_cast.const_cast.reinterpret_cast static_cast: 编译器隐式运行的不论什么类型转 ...

  5. C++ 异常机制分析

    C++异常机制概述 异常处理是C++的一项语言机制,用于在程序中处理异常事件.异常事件在C++中表示为异常对象.异常事件发生时,程序使用throw关键字抛出异常表达式,抛出点称为异常出现点,由操作系统 ...

  6. 【转载】C++异常机制的学习

    参考了这篇文章:http://blog.chinaunix.net/uid-24517549-id-4079174.html 关于线程 进程和线程的概念相信各位看官早已耳熟能详.在这里,我只想带大家回 ...

  7. 黑马程序员——【Java基础】——面向对象(二)异常机制、包(Package)

    ---------- android培训.java培训.期待与您交流! ---------- 一.异常机制 (一)异常概述 1.异常:就是程序在运行时出现不正常情况. 2.异常类:程序在运行时,出现的 ...

  8. C++异常机制知识点

     在这里总结一下,C++中的异常机制,以及如何使用异常的知识点 C++中处理异常的过程是这样的:在执行程序发生异常,可以不在本函数中处理,而是抛出一个错误信息,把它传递给上一级的函数来解决,上一级解决 ...

  9. [转载]C++异常机制的实现方式和开销分析

    原文章网址:http://baiy.cn/doc/cpp/inside_exception.htm C++异常机制的实现方式和开销分析 白杨 http://baiy.cn 在我几年前开始写<C+ ...

随机推荐

  1. 移动开发 android 入门开发 阶段视频

    一直想把 android 的开发学习录制成视频,这里录制了一部分供大家学习. http://www.chuanke.com/s5402069.html 到这里,文档,源码,视频基本就全了,祝愿大家能够 ...

  2. Oracle 事务

    begin begin savepoint p1; DELETE FROM sys_re_xxx; //红色部分替换为需要一起执行的SQL即可 DELETE FROM SYS_xxxx; ...... ...

  3. java多态例子

    多态存在的三个必要条件一.要有继承:二.要有重写:三.父类引用指向子类对象. 代码部分: class A { public String show(D obj) { return ("A a ...

  4. NOIP1998 拼数

    http://www.luogu.org/problem/show?pid=1012 题目描述 设有n个正整数(n≤20),将它们联接成一排,组成一个最大的多位整数. 例如:n=3时,3个整数13,3 ...

  5. 《Ossim应用指南》入门篇

    Ossim应用入门 --在<OSSIM在企业网络管理中的应用>http://chenguang.blog.51cto.com/350944/802007 这篇文章发布之后,很多同行对oss ...

  6. 【Linux】inode_针对MySQL读写操作在系统层的进一步学习【转】

    转自http://www.cnblogs.com/itech/archive/2012/05/15/2502284.html 一.inode是什么? 理解inode,要从文件储存说起. 文件储存在硬盘 ...

  7. LiveView 0.8 RC1 could boot evidence files acquired from Win10 64bit

    The latest Windows 10 will be more and more popular in the very near future. Now let's take a look i ...

  8. IP配置

    1: #vi /etc/sysconfig/network-scripts/ifcfg-eth0 2: 实验环境-网络设置 公司域网: IP=162.168.16.0/24 netmask=255.2 ...

  9. js怎样改变div的宽度

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  10. SQLserver2012 修改数据库架构

    还原数据库以后,发现有一张表的架构不对,执行sql提示:对象名无效.