免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: vupiggy
打印 上一主题 下一主题

[C] switch-case 非常规用法拾零 (1. 绝无原创 2. 非常重视代码规范者勿入) [复制链接]

论坛徽章:
2
亥猪
日期:2014-03-19 16:36:35午马
日期:2014-11-23 23:48:46
11 [报告]
发表于 2010-12-17 14:50 |只看该作者
牛X。语法都清楚,能玩出来就是牛

论坛徽章:
0
12 [报告]
发表于 2010-12-17 15:13 |只看该作者
感谢能阅读这么好的文章,虽然作者也说这种风格对未加训练的团队(也就是大家不熟悉这种太灵活的用法,或者你的队友和你不在同一个层次)不好。但这在驱动上也够用了。

论坛徽章:
0
13 [报告]
发表于 2010-12-17 15:21 |只看该作者
玩得不错,不过一般代码用来阅读和维护的

论坛徽章:
0
14 [报告]
发表于 2010-12-17 17:01 |只看该作者
switch case 的方法见到过,就是将一个完整的函数分割成多个部分的小函数的时候,这个手法很用

论坛徽章:
0
15 [报告]
发表于 2010-12-17 23:55 |只看该作者
mark 研究下

论坛徽章:
0
16 [报告]
发表于 2010-12-18 00:37 |只看该作者
本帖最后由 vupiggy 于 2010-12-17 17:45 编辑

策略和机制分离。我的理解是:机制部分应该只涵盖利用 switch-case 的语法特性来实现跳转,至于如何使用这个机制统统属于策略。像文中那么简单的例子只是为了说明机制可以工作。

我感觉这方案没能解决。
...
1. 保存执行的context —— 用static, 并带来static的各种问题
OwnWaterloo 发表于 2010-12-17 04:32

通过传入指向 context 的指针可以解决。在 putty 的实现代码里实际上使用的就是非 static 变量版本:
定义:

  1. #define crBegin(v) { int *crLine = &v; switch (v) { case 0:;
  2. #define crState(t) \
  3.     struct t *s; \
  4.     if (!ssh->t) ssh->t = snew(struct t); \
  5.     s = ssh->t;
复制代码
注:snew 可以看作 malloc

使用方式:

  1. static int do_ssh_init(Ssh ssh, unsigned char c)
  2. {
  3.     struct do_ssh_init_state {
  4.         int vslen;
  5.         char version[10];
  6.         char *vstring;
  7.         int vstrsize;
  8.         int i;
  9.         int proto1, proto2;
  10.     };
  11.     crState(do_ssh_init_state);

  12.     crBegin(ssh->do_ssh_init_crstate);
复制代码
2. 提供一种机制区分yield和return —— 没有解决

利用返回值可以区分。function() 函数是错了,那是写程序人的错,没有处理循环体结束的情况,但并不代表这种机制不提供这个能力。decompressor() 最后的

  1. crReturn(EOF);
  2. crFinish;
复制代码
即告知调用者一次任务完成,cfFinish则开始善后,结束这个routine的生命周期。在putty里实际使用的代码像下面这样:

  1. #define crFinish(z)        } *crLine = 0; return (z); }
复制代码
充其量, 这方案仅仅是让语法稍微好看那么一点。

对简单的应用, 搞那么复杂不如直接在外部写一个for循环。
对复杂的应用, 这种方案又不具备范化的可能。

充其量这个词用得有一定侮辱性。Simon Tatham 老哥听了可能会有点不爽,不过我不会告诉他

好吧,没有人否认这种 Coroutine 的实现有很大的局限性,但是对于一些非常特定情况,譬如说中不溜的应用,过程调用层比较深,而各个过程的内部状态又比较复杂,譬如网络协议,则个方案有一定实用性。像 putty 的需求,就不是在外部多写一个个 for 或者是 while 可以解决的了。

我没找到其它的 C 下实现 coroutine 的方案,这是 C 自身特点所限,stack based function。用汇编做栈切换当然可以完美解决,“but on the other hand it beats having to write the stack switcher separately for every different platform and compiler on which I plan to run my code. Porting is more than enough hassle as it is...”--- Simon Tatham

另外就是 Tom Duff 自己其实也独立给出过这个 trick。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
17 [报告]
发表于 2010-12-18 05:07 |只看该作者
16楼 发表于 2010-12-18 00:37
本帖最后由 vupiggy 于 2010-12-17 17:45 编辑

这个……  你怎么做到的……

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
18 [报告]
发表于 2010-12-18 05:09 |只看该作者

关于非static的case, 能给出一个完整的例子么?

将下面这个lua的例子翻译为C好了:

function f() --> f分2个步骤, 可在执行完第1个步骤后挂起
  print('in', coroutine.running(), 'step1')
  --> 输出当前running只是为了下面的区分, 并不是重点
  coroutine.yield()
  print('in', coroutine.running(), 'step2')
end

c1 = coroutine.create(f) --> 建立2个coroutine
c2 = coroutine.create(f)
=c1 --> thread: 003ECC18 --> 输出coroutine的id
=c2 --> thread: 003EE738

-> 上面的coroutine.running
-> 只是为了这里能比较清楚的区分
coroutine.resume(c1) -> in      thread: 003ECC18        step1
coroutine.resume(c2) -> in      thread: 003EE738        step1
coroutine.resume(c2) -> in      thread: 003EE738        step2
coroutine.resume(c1) -> in      thread: 003ECC18        step2

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
19 [报告]
发表于 2010-12-18 05:14 |只看该作者

RC4加密程序

本帖最后由 OwnWaterloo 于 2010-12-18 05:17 编辑

Simon的那个例子我觉得还不够具体, 比如got_token、add_to_token什么的没有给出。
所以我换一个完整的例子, 与RC4加密有关。
RC4的描述见: http://en.wikipedia.org/wiki/RC4#Description

RC4算法大致是:
1. 通过key初始化一个长度为256的byte数组
2. 从这个byte数组中进行某种置换操作, 并输出1个byte
3. 重复步骤2, 就可以得到一个byte流
4. 将明文的byte流与3得到的byte流进行异或操作, 得到密文

1. RC4加密算法伪C语言描述

重点就是得到异或byte流, 算法如下:

  1. unsigned char rc4_stream(char const* key, size_t len)
  2. {
  3.       // init S
  4.       unsigned char S[256];
  5.       for (unsigned i=0; i<256; ++i)
  6.             S[i] = i;

  7.       for (unsigned i=0, j=0; i<256; ++i)
  8.       {
  9.             j = (j+S[i]+key[i%len]) % 256;
  10.             swap(S[i], S[j]);
  11.       }

  12.       // generate stream
  13.       for (unsigend i=0, j=0; ; )
  14.       {
  15.             i = (i+1) % 256;
  16.             j = (j+S[i]) % 256;
  17.             swap(S[i], S[j]);
  18.             yield(S[ (S[i]+S[j]) % 256 ]);
  19.       }
  20. }
复制代码
我想这个例子比Simon更完整与具体, 整个代码不再与任何其他事物相关。
(除了swap、 它应该不需要更多解释)。

代码里有一个C语言不支持的yield, 所以是伪C代码。
若用真正的C语言实现, 就需要保存产生stream序列的最后一个for循环中context。
具体的说, 就是S, i, j。
下面给出一个实现。

2. RC4加密, C++完整实现

  1. #include <stdio.h>
  2. #include <algorithm>

  3. // 为了代码的可读性, 稍微用了一点C++的东西
  4. // 代码中有说明

  5. typedef struct
  6. {
  7.       unsigned i, j;
  8.       unsigned char s[256];
  9. } rc4_t;

  10. void rc4_ctor(rc4_t* self, char const* key, size_t len)
  11. {
  12.       unsigend char* S = self->s;
  13.       for (unsigend i=0; i<256; ++i)
  14.             S[i] = i;
  15.       for (unsigned i=0, j=0; i<256; ++i)
  16.       {
  17.             j = (j+S[i]+key[i%len]) % 256;
  18.             std::swap(S[i], S[j]); // 懒得写一个swap了
  19.       }
  20.       self->i = self->j = 0;
  21. }

  22. unsigend rc4_next(rc4_t* self)
  23. {
  24.       unsigend& i = self->i;
  25.       unsigend& j = self->j;
  26.       unsigend char* S = self->s;

  27.       // 使用引用是为了下面几行置换代码短一些
  28.       i = (i+1) % 256;
  29.       j = (j+S[i]) % 256;
  30.       std::swap(S[i], S[j]);

  31.       return S[ (S[i]+S[j]) % 256 ];
  32. }

  33. int main(void)
  34. {
  35.       // 为了简单, 硬编码, 且没有任何错误处理
  36.       FILE* a = fopen("a", "rb");
  37.       FILE* b = fopen("b", "wb");
  38.       rc4_t g;

  39.       rc4_ctor(&g, "1212", 4);
  40.       for (int c; c=getc(a), c!=EOF;)
  41.             putc( c ^ rc4_next(&g, b) );

  42.       fclose(b);
  43.       fclose(a);
  44.       return 0;
  45. }

复制代码
对比两种实现, 我觉得使用coroutine的伪代码版本更贴近算法本身。
而C/C++不支持coroutine, 不得由大脑分析出context是哪些, 并手工保存(如rc4_t)。

RC4加密, 我已经给出实现了。
而lua的那个例子, 我相信用C肯定是可以实现的。
但我认为这都不算coroutine。
理由是这些实现方式并没有体现出coroutine的任何优势尤其是脑力上的优势
真正的coroutine不需要用户去考虑挂起点, 比如lua的例子, 用C实现的话要函数中设置一个状态机, 再按state去switch或者goto;
不需要用户去考虑哪些local variable是恢复执行时需要保存的context, 比如RC4中的S,i,j。

而当用户把这些状态机, context考虑清楚后, 实际上已经完成了人工实现coroutine所需要的脑力活动, 不需要coroutine也可以实现程序。
那还在程序中加入一个多数人一眼无法理解的机制做什么? 直接按最朴质的方案做就是了。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
20 [报告]
发表于 2010-12-18 05:29 |只看该作者

C下实现coroutine的库

C下实现coroutine的库 —— 不需要用户去考虑如何设置状态机, 保存context的方案—— 是有的。
coroutine的wiki页面应该就有介绍一些, 我没仔细研究。
用thread肯定可以实现, 但那代价太大, 只有理论上的价值。

下面我演示一个使用win32 fiber模拟coroutine的简陋方案。
因为coroutine的切换本来就是同步的, 所以可以避免使用thread和同步工具。
而且fiber的切换是用户态的, 只有10左右的机器指令。

coroutine的api。

  1. // coroutine.h

  2. typedef struct
  3. {
  4.       void* fiber_;
  5.       void (*f_)(void* x);
  6.       void* x_;
  7.       void* y_;
  8.       void* caller_;
  9. } coroutine_t;

  10. // 这是因为win32 fiber的限制
  11. // 每个使用fiber的thread必须首先做一些工作
  12. int   coroutine_init(void);

  13. int   coroutine_create(coroutine_t* self, void (*f)(void* x), void* x);
  14. // C没有垃圾收集等东西, 所以需要手动释放资源
  15. void  coroutine_destory(coroutine_t* self);

  16. // resume返回yield的参数
  17. void* coroutine_resume(coroutine_t* self);
  18. void  coroutine_yield(void* val);

  19. // 同lua coroutine.running
  20. void* coroutine_running(void);

复制代码
那么, lua的例子就可以改写为:

  1. // lua_example.c

  2. #include <stdio.h>

  3. #include "coroutine.h"

  4. void f(void* arg)
  5. {
  6.       printf("in %p step1\n", coroutine_running() );
  7.       coroutine_yield(0);
  8.       printf("in %p step2\n", coroutine_running() );
  9. }

  10. int main(void)
  11. {
  12.       coroutine_t c1, c2;

  13.       coroutine_init();

  14.       coroutine_create(&c1, f, 0);
  15.       coroutine_create(&c2, f, 0);

  16.       printf("coroutine1 %p\n", (void*)&c1);
  17.       printf("coroutine2 %p\n", (void*)&c2);

  18.       coroutine_resume(&c1);
  19.       coroutine_resume(&c2);
  20.       coroutine_resume(&c2);
  21.       coroutine_resume(&c1);

  22.       coroutine_destory(&c2);
  23.       coroutine_destory(&c1);

  24.       return 0;
  25. }
复制代码
使用coroutine的rc4:

  1. // rc4.cpp

  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <algorithm>

  5. #include "coroutine.h"

  6. void rc4_stream(void* arg)
  7. {
  8.       unsigned char* key = (unsigned char*)arg;
  9.       unsigned char S[256];

  10.       for (unsigned i=0; i<256; ++i)
  11.             S[i] = i;

  12.       for (size_t i=0, j=0, len=strlen((char*)key); i<256; ++i)
  13.       {
  14.             j = (j+S[i]+key[i%len]) % 256;
  15.             std::swap(S[i], S[j]);
  16.       }

  17.       for (unsigned i=0, j=0; ; )
  18.       {
  19.             i = (i+1) % 256;
  20.             j = (j+S[i]) % 256;
  21.             std::swap(S[i], S[j]);
  22.             coroutine_yield((void*)(S[ (S[i]+S[j]) % 256 ]));
  23.       }
  24. }

  25. int main(void)
  26. {
  27.       FILE* a = fopen("a", "rb");
  28.       FILE* b = fopen("b", "wb");
  29.       coroutine_t g;

  30.       coroutine_init();
  31.       coroutine_create(&g, rc4_stream, (void*)"1212");
  32.       for (int c; c=getc(a), c!=EOF; )
  33.             putc((unsigned)coroutine_resume(&g) ^ c, b);
  34.       coroutine_destory(&g);
  35.       fclose(b);
  36.       fclose(a);
  37.       return 0;
  38. }
复制代码
请留意一下f与rc4_stream函数的代码, 可以在心里默默的"输入"试试。
是否完全不需要考虑挂起点与context的保存, 直接实现算法就可以了?
这就是coroutine的脑力优势。

当然, 这种方案肯定有缺点。

首先肯定因为使用了win32特性而不可移植。
但可以找一些跨平台的coroutine的库, 如果它们很稳定的话。
最重要的是: C语言真的需要那些花花肠子的功能吗
我感觉并不是所有时候都需要把C武装到牙齿, 依然用简单的C实现关键的部分, 其余部分交给胶水语言去粘就完了。
因为简单是C的目标之一, 无论怎么武装, 它都是缺这缺哪的

下面先帖一下coroutine的实现代码, 再说关于coroutine, C语言缺了哪些东西。

  1. // coroutine.c

  2. #define _WIN32_WINNT 0x0400
  3. #include <windows.h>

  4. #include "coroutine.h"

  5. int  coroutine_init()
  6. {
  7.       return ConvertThreadToFiber(0) != 0;
  8. }

  9. void* coroutine_running(void)
  10. {
  11.       return GetFiberData();
  12. }

  13. static void __stdcall coroutine_start_(void* arg)
  14. {
  15.       coroutine_t* self = (coroutine_t*)arg;
  16.       self->f_(self->x_);
  17.       SwitchToFiber(self->caller_);
  18. }

  19. int  coroutine_create(coroutine_t* self, void (*f)(void* x), void* x)
  20. {
  21.       self->f_ = f;
  22.       self->x_ = x;
  23.       self->fiber_ = CreateFiber(0, coroutine_start_, self);
  24.       self->y_ = 0;
  25.       return self->fiber_!=0;
  26. }

  27. void  coroutine_destory(coroutine_t* self)
  28. {
  29.       DeleteFiber(self->fiber_);
  30. }

  31. void* coroutine_resume(coroutine_t* self)
  32. {
  33.       self->caller_ = GetCurrentFiber();
  34.       SwitchToFiber(self->fiber_);
  35.       return self->y_;
  36. }

  37. void coroutine_yield(void* val)
  38. {
  39.       coroutine_t* self = (coroutine_t*)GetFiberData();
  40.       self->y_ = val;
  41.       SwitchToFiber(self->caller_);
  42. }
复制代码
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP