Chinaunix首页 | 论坛 | 博客
  • 博客访问: 72088
  • 博文数量: 23
  • 博客积分: 684
  • 博客等级: 上士
  • 技术积分: 335
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-28 13:43
文章分类
文章存档

2011年(4)

2010年(19)

分类: LINUX

2010-12-30 17:22:12

0. 前言
以下介绍的两个大牛都是利用了 switch 语句中的 case 部分可以置于一个
block (通常是 if{} 或 while{} 块)中, switch 语句执行一个直接跳转进入该
block,进入该 block 的条件有时候被有意地忽略。

全文思路和代码绝无原创之处,只是略加整理了一下。更详细的请看提供的链接。
希望高手老鸟们指出错误不足并给出更深入的分析,如果能提出比例子更漂亮的
方案就太棒了(我感觉可以用回调函数一类的方法实现协作过程,不过还没细想)。

代码规范的讨论就请免了,Simon Taham 在文章里说了一段话挺有趣:

Any coding standard which insists on syntactic clarity at the expense
of algorithmic clarity should be rewritten. If your employer fires you
for using this trick, tell them that repeatedly as the security staff
drag you out of the building. hahaha

1. Duff's device (原创 Tom Duff)
%27s_device

下面一段小程序:
  1. do {
  2.     *to++ = *from++;
  3. } while (--count > 0);
这段程序最让大牛们不爽的是,每拷贝一个字节就需要完成和拷贝字节同量级甚
至更耗时的减法及比较操作,一个可能的优化是减少循环次数,即在一次减法和
比较之后批处理拷贝几个字节,可能的代码如下:
  1. do {
  2.     *to++ = *from++;
  3.     *to++ = *from++;
  4.     *to++ = *from++;
  5.     *to++ = *from++;
  6.     *to++ = *from++;
  7.     *to++ = *from++;
  8.     *to++ = *from++;
  9.     *to++ = *from++;
  10. } while ((count -= 8) > 0);
这段代码是错的,当且仅当 count 被 8 整除才工作,这个思路总体是正确的,
但最大的挑战是如何处理余数。Tom 大叔巧妙的解决余数问题,代码如下:
  1. int n = (count + 7) / 8;
  2. switch (count % 8) {
  3.         case 0:        do {  *to++ = *from++;
  4.         case 7:              *to++ = *from++;
  5.         case 6:              *to++ = *from++;
  6.         case 5:              *to++ = *from++;
  7.         case 4:              *to++ = *from++;
  8.         case 3:              *to++ = *from++;
  9.         case 2:              *to++ = *from++;
  10.         case 1:              *to++ = *from++;
  11.                        } while (--n > 0);
  12. }
过程一开始,switch 语句计算余数,直接跳转到对应 case 地址,妙处就在除了
case 0,所有的 case 都在 do {} while() 中,第一次拷贝 count % 8 个
字节,之后逻辑由 do {} while() 循环负责,一次循环拷贝 8 个字节直至结束。

注: 例子其实是 Stroustrup 的新手友好版本而不是 Tom 大叔的原始版本,但
是非常接近。这个版本可以再精简一点点,抛弃变量 n,将 while(--n > 0) 改
  1. while ((count -= 8) > 0)
2. Simon Tatham's Coroutines in C (原创 putty的作者 Simon Tatham)
这位牛牛要解决的问题复杂一些,即所谓“协作过程”(coroutine)问题。


2.1 引子
考虑如下函数:
  1. int function(void) {
  2.     int i;
  3.     for (i = 0; i < 10; i++)
  4.         return i;   /* won't work, but wouldn't it be nice? */
  5. }
以上函数的本意是向调用者依次返回从0到9,很显然结果是每一次都返回了0,
将 i 声明为 static 也无济于事。一个可行的方案是:
  1. int function(void) {
  2.     static int i, state;

  3.     switch(state) {
  4.         case 0: goto LABEL0;
  5.         case 1: goto LABEL1;
  6.     }

  7.     /* start of function */
  8. LABEL0:
  9.     for (i = 0; i < 10; i++) {
  10.         state = 1;
  11.         return i;
  12. LABEL1:; /* Resume control straight after return */
  13.     }
  14. }
以上代码最严重的问题是当有多点返回的时候,程序员要手工维护跳转的标签及
一一对应的 case 段。利用上面提到的 case 段可以置于块内的特点,上面的函
数可以改写成:
  1. int function(void) {
  2.     static int i, state = 0;

  3.     switch (state) {
  4.         case 0: /* start of function */

  5.         for (i = 0; i < 10; i++) {
  6.             state = 1; /* so we will come back to "case 1" */
  7.             return i;

  8.             case 1:; /* resume control straight after the return */
  9.         }
  10.     }
  11. }
再进一步提高代码可读性,使用几个宏:
  1. #define crBegin static int state=0; switch(state) { case 0:
  2. #define crReturn(i,x) do { state=i; return x; case i:; } while (0)
  3. #define crFinish }

  4. int function(void) {
  5.     static int i;

  6.     crBegin;

  7.     for (i = 0; i < 10; i++)
  8.         crReturn(1, i);

  9.     crFinish;
  10. }
这下看上去像一个“正常”的 C 程序了吧 还有一个小问题,程序员还要自己想
法保证 crReturn() 的第一个参数是唯一的,烦!接着 hack,把这个宏定义成
这个样子利用编译器来帮我们保证参数唯一:
  1. #define crReturn(x) do { state=__LINE__; return x; case __LINE__:; } while (0)
搞掂晒,程序员的重要品质就是能偷懒就偷懒,能不手工(眼工)做的就不做。

2.2 协作过程
在编写 putty 的过程中,这个大牛发明了上述用法来解决所谓协作过程的问题。
考虑以下两个例程:
  1. /* Decompression code */
  2. int decompressor(void) {
  3.     while (1) {
  4.         c = getchar();
  5.         if (c == EOF)
  6.             break;
  7.         if (c == 0xFF) {
  8.             len = getchar();
  9.             c = getchar();
  10.             while (len--)
  11.                 emit(c);
  12.         } else
  13.             emit(c);
  14.     }
  15.     emit(EOF);
  16. }
  1. /* Parser code */
  2. void parser(int c) {
  3.     while (1) {
  4.         c = getchar();
  5.         if (c == EOF)
  6.             break;
  7.         if (isalpha(c)) {
  8.             do {
  9.                 add_to_token(c);
  10.                 c = getchar();
  11.             } while (isalpha(c));
  12.             got_token(WORD);
  13.         }
  14.         add_to_token(c);
  15.         got_token(PUNCT);
  16.     }
  17. }
姑且把解压例程理解为生产者而语法解析例程为消费者,如果不考虑使用管道,
多线程等重量级解决方案,那么必然有函数调用关系,姑且是认为解析例程调用
解压例程,也就是把解析例程中的每一个 getchar() 替换成 decompressor(),
并把 decompressor() 中的每一个 emit 替换成 return。但是问题来了,每一
次调进 decompressor 以后,总是从头开始执行,这不满足要求,正确的行为应
该是每次调进 decompressor() 都从上一次 return 的地方继续执行,否则就不
叫协作了。采用引子里提到的三个宏构建一个状态机即可完美解决协作需求:
  1. int decompressor(void) {
  2.     static int c, len;
  3.     crBegin;
  4.     while (1) {
  5.         c = getchar();
  6.         if (c == EOF)
  7.             break;
  8.         if (c == 0xFF) {
  9.             len = getchar();
  10.             c = getchar();
  11.             while (len--)
  12.                 crReturn(c);
  13.         } else
  14.             crReturn(c);
  15.     }
  16.     crReturn(EOF);
  17.     crFinish;
  18. }
用 cpp 来预处理一下可以帮助理解。
阅读(2174) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~