这段代码怎么这么奇怪?

papaofdoudou
人工智能领域新星创作者
博客专家认证
2009-05-05 09:04:07
void send(int* to,int* from,int count)
{
int n=(count+7)/8;
switch(count%8)
{
case 0:
do
{
*to++=*from++;
case 7: *to++=*from++;
case 6: *to++=*from++;
case 5: *to++=*from++;
case 4: *to++=*from++;
case 3: *to++=*from++;
case 2: *to++=*from++;
case 1: *to++=*from++;

}while(--n>0);
}
}
上面的代码没有错误,能编译运行的,而且是出自大师之手的代码,但是我怎么看怎么觉的别扭,具体又说不出哪里别扭,先提几个问题吧:
1.case 0和其他的case语句地位是平等的吗?很明显其他的case语句都被包含在语句块里了,但是在此语句块里并没有另外的一个switch语句,那么这写case语句是不是属于case 0情况下的一段代码呢??也就是说,即是count%8为6,也不会执行case 6:那一行,因为外部的switch语句只能看到case 0语句,内部case语句的被包含起来了,是这样吗??

...全文
248 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
niuchengshi 2010-12-28
  • 打赏
  • 举报
回复
如果清楚case可以在块内仍然有效的话,这个写法还真是很高效。
INININ666 2009-05-05
  • 打赏
  • 举报
回复
这段代码很有意思的,她的意思是对case1~case7可以循环执行多次,case0的地位和case1~case7是一样的,这段代码的关键点就是do…while…语句,总之代码相当有意思!
baihacker 2009-05-05
  • 打赏
  • 举报
回复
我是来仰望的...
taodm 2009-05-05
  • 打赏
  • 举报
回复
兄弟,知道这代码出现的时代,那时的计算机主流配置是什么吗?
当年的C语言编程技巧大赛现在已经叫混沌代码大赛了。
注意约束条件的变化,要与时具进。
Paradin 2009-05-05
  • 打赏
  • 举报
回复
20.30 什么是“达夫设备” (Duff’s Device)?
这是个很棒的迂回循环展开法, 由Tom Duff 在Lucasfilm 时所设计。它的“传
统” 形态, 是用来复制多个字节:
register n = (count + 7) / 8; /* count > 0 assumed */
switch (count % 8)
{
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
这里count 个字节从from 指向的数组复制到to 指向的内存地址(这是个内
存映射的输出寄存器, 这也是为什么它没有被增加)。它把swtich 语句和复制8 个
字节的循环交织在一起, 从而解决了剩余字节的处理问题(当count 不是8 的倍数
时)。相信不相信, 象这样的把case 标志放在嵌套在swtich 语句内的模块中是合法
的。当他公布这个技巧给C 的开发者和世界时, Duff 注意到C 的swtich 语法, 特
别是“跌落” 行为, 一直是被争议的, 而“这段代码在争论中形成了某种论据, 但我
不清楚是赞成还是反对”。
帅得不敢出门 2009-05-05
  • 打赏
  • 举报
回复
1,循环展开
2,判断减少
  • 打赏
  • 举报
回复
反正,你觉得你平时会这么些代码么。
wangsiyuanoo 2009-05-05
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 tugouxp 的回复:]
这个大师说出来吓坏众人,他就是人见人爱,花见花开,车间车爆胎的bjarne stroustrup
[/Quote]
哇,C++他爹~~~~
mo229mo 2009-05-05
  • 打赏
  • 举报
回复
case 0和其他的case语句地位是平等的.
自己跟一下就一目了然了。
不过这样的代码不管是干什么的,都应该修正。
不要盲目信任所谓的大师!
papaofdoudou 2009-05-05
  • 打赏
  • 举报
回复
这个大师说出来吓坏众人,他就是人见人爱,花见花开,车间车爆胎的bjarne stroustrup
goodname 2009-05-05
  • 打赏
  • 举报
回复
http://c-faq-chn.sourceforge.net/ccfaq/node380.html


21.30 什么是 ``达夫设备" (Duff's Device)?
这是个很棒的迂回循环展开法, 由 Tom Duff 在 Lucasfilm 时所设计。它的 ``传统" 形态, 是用来复制多个字节:

register n = (count + 7) / 8; /* count > 0 assumed */
switch (count % 8)
{
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}

这里 count 个字节从 from 指向的数组复制到 to 指向的内存地址 (这是个内存映射的输出寄存器, 这也是为什么它没有被增加)。它把 swtich 语句和复制 8 个字节的循环交织在一起, 从而解决了剩余字节的处理问题 (当 count 不是 8 的倍数时)。相信不相信, 象这样的把 case 标志放在嵌套在 swtich 语句内的模块中是合法的。当他公布这个技巧给 C 的开发者和世界时, Duff 注意到 C 的 swtich 语法, 特别是 ``跌落" 行为, 一直是被争议的, 而 ``这段代码在争论中形成了某种论据, 但我不清楚是赞成还是反对"。
lzh9955 2009-05-05
  • 打赏
  • 举报
回复
伤脑筋的代码!!
mengde007 2009-05-05
  • 打赏
  • 举报
回复
哪位大师的作品??
mengde007 2009-05-05
  • 打赏
  • 举报
回复
哪位大师的作品??
ch_tei_hyou 2009-05-05
  • 打赏
  • 举报
回复
是能调到的吧!
奇怪是因为没有break语句;
但是在需要的时候不要break语句也是可以的;
lz应该再看下swich case 语句的用法和注意点 - -
goodname 2009-05-05
  • 打赏
  • 举报
回复
http://cq.netsh.com/bbs/755885/html/tree_24898781.html


俺从94年开始玩C,98年中上了网,在COMP.LANG.C中看到这段代码,心里的震撼真是难以形容。当时也自以为C已学透了自认为也都有点心得了。但这段代码对C关键词的运用从根本上改变了俺对自己和C的认识:

它的‘学名’叫 Duff's Device'. 各位要有兴趣可自己上网去搜。

/******************************************/
register n = (count + 7) / 8; /* count > 0 assumed */
switch (count % 8)
{
case 0: do { *to = *from++;
case 7: *to = *from++;
case 6: *to = *from++;
case 5: *to = *from++;
case 4: *to = *from++;
case 3: *to = *from++;
case 2: *to = *from++;
case 1: *to = *from++;
} while (--n > 0);
}
/******************************************/
John_Lee 发表于 2005-9-5 11:02 AVR 单片机 ←返回版面 按此察看该网友的资料 按此把文章加入收藏夹 按此编辑本帖

这段代码的作用等效于 memcpy (to, from, count),但有一点区别:两者的 count 参数定义域不同!

但作者为什么不用 memcpy 而要大费周章地写一段比较晦涩的内存复制程序呢?我们来仔细分析一下(因为这里是 AVR 论坛,我就以 AVR 为运行平台,编译程序用 avr-gcc)。

我把这段程序写成了一个函数,且称为 fast_memcpy:

#include <avr/io.h>
#include <stdlib.h>

void fast_memcpy (uint8_t *to, uint8_t *from, size_t count)
{
register size_t n = (count + 7) / 8; /* count > 0 assumed */
uint8_t c = count % 8;
switch (c)
{
case 0: do { *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
} while (--n > 0);
}
}

而标准的 memcpy,大家都想得到应该是:

#include <avr/io.h>
#include <stdlib.h>

void memcpy (uint8_t *to, uint8_t *from, size_t count)
{
while (count)
{
--count;
*to++ = *from++;
}
}

编译后的结果正如我们预料的一样,fast_memcpy 编译出来的代码比 memcpy 的代码长,大家可以自己试一试。

既然 fast_memcpy 在代码长度上不占优势,那么就只有在运行速度上做文章了。我们先分析 memcpy:

memcpy 编译后的代码如下:
void memcpy (uint8_t *to, uint8_t *from, size_t count)
{
0: dc 01 movw r26, r24
2: fb 01 movw r30, r22
while (count)
{
4: 41 15 cp r20, r1
6: 51 05 cpc r21, r1
8: 29 f0 breq .+10 ; 0x14
--count;
a: 41 50 subi r20, 0x01 ; 1
c: 50 40 sbci r21, 0x00 ; 0
*to++ = *from++;
e: 81 91 ld r24, Z+
10: 8d 93 st X+, r24
}
12: f8 cf rjmp .-16 ; 0x4
}
14: 08 95 ret

在 avr 中,复制一个字节最基本的代码是:一个 ld 指令加 一个 st 指令,共 4 个 指令周期,这是最小开销了。

memcpy 程序执行主要开销在循环体中(循环体之前的开销可以忽略不计),每个循环复制一个字节,循环占 11 个左右时钟周期。它的效率就是 4 / 11 = 36%。

我们再看 fast_memcpy:

void fast_memcpy (uint8_t *to, uint8_t *from, size_t count)
{
0: dc 01 movw r26, r24
2: fb 01 movw r30, r22
4: ca 01 movw r24, r20
register size_t n = (count + 7) / 8; /* count > 0 assumed */
6: 07 96 adiw r24, 0x07 ; 7
8: 9c 01 movw r18, r24
a: 43 e0 ldi r20, 0x03 ; 3
c: 36 95 lsr r19
e: 27 95 ror r18
10: 4a 95 dec r20
12: e1 f7 brne .-8 ; 0xc
14: 07 97 sbiw r24, 0x07 ; 7
uint8_t c = count % 8;
16: 87 70 andi r24, 0x07 ; 7
switch (c)
18: 83 30 cpi r24, 0x03 ; 3
1a: d1 f0 breq .+52 ; 0x50
1c: 84 30 cpi r24, 0x04 ; 4
1e: 28 f4 brcc .+10 ; 0x2a
20: 81 30 cpi r24, 0x01 ; 1
22: d1 f0 breq .+52 ; 0x58
24: 82 30 cpi r24, 0x02 ; 2
26: b0 f4 brcc .+44 ; 0x54
28: 09 c0 rjmp .+18 ; 0x3c
2a: 85 30 cpi r24, 0x05 ; 5
2c: 69 f0 breq .+26 ; 0x48
2e: 85 30 cpi r24, 0x05 ; 5
30: 68 f0 brcs .+26 ; 0x4c
32: 86 30 cpi r24, 0x06 ; 6
34: 39 f0 breq .+14 ; 0x44
36: 87 30 cpi r24, 0x07 ; 7
38: 19 f0 breq .+6 ; 0x40
3a: 08 95 ret
{
case 0: do { *to++ = *from++;
3c: 81 91 ld r24, Z+
3e: 8d 93 st X+, r24
case 7: *to++ = *from++;
40: 81 91 ld r24, Z+
42: 8d 93 st X+, r24
case 6: *to++ = *from++;
44: 81 91 ld r24, Z+
46: 8d 93 st X+, r24
case 5: *to++ = *from++;
48: 81 91 ld r24, Z+
4a: 8d 93 st X+, r24
case 4: *to++ = *from++;
4c: 81 91 ld r24, Z+
4e: 8d 93 st X+, r24
case 3: *to++ = *from++;
50: 81 91 ld r24, Z+
52: 8d 93 st X+, r24
case 2: *to++ = *from++;
54: 81 91 ld r24, Z+
56: 8d 93 st X+, r24
case 1: *to++ = *from++;
58: 81 91 ld r24, Z+
5a: 8d 93 st X+, r24
} while (--n > 0);
5c: 21 50 subi r18, 0x01 ; 1
5e: 30 40 sbci r19, 0x00 ; 0
60: 69 f7 brne .-38 ; 0x3c
}
62: 08 95 ret
}

尽管 fast_memcpy 代码较长,但它仍然有一个循环体,循环体开销 36 个时钟周期,但和 memcpy 不同的是,它在循环体中一次循环复制 8 个字节。它的效率是 32 / 36 = 89%。

必须要提到的是 fast_memcpy 中循环体之前的程序在干什么?它的开销怎么说?

register size_t n = (count + 7) / 8; /* count > 0 assumed */
uint8_t c = count % 8;
switch (c)

这段程序的作用是:计算 count / 8 的“商”和“余数”,“商”用于循环次数计数,“余数”用于决定循环体的最初起点。简单地说:先复制“余数”个字节,再复制“商” * 8 个字节。它的开销大概有 14 ~ 35 个时钟周期。这对于只有几个字节的复制,总开销就比较大,效率可能还不如 memcpy。

开销对比如下:

memcpy 周期:count * 11
fast_memcpy 周期:14 ~ 35 + (count + count / 8) * 4

总的来说,fast_memcpy 是一个非常不错的编程技巧,它可以用在那些频繁复制内存,并且对效率要求很高的程序中。
papaofdoudou 2009-05-05
  • 打赏
  • 举报
回复
回复楼上各位,发现的确能调用到,就跟正常的switch语句一样!这就更纳闷了
wangsiyuanoo 2009-05-05
  • 打赏
  • 举报
回复
我也想知道。。。
帮顶
帅得不敢出门 2009-05-05
  • 打赏
  • 举报
回复
在case下输出就知道有没有调用了.
我看你有戏 2009-05-05
  • 打赏
  • 举报
回复

实际编程中尽量不要出现这种伤脑筋的代码

64,648

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧