分享
 
 
 

更深入一点理解switch语句及c/c++对const的处理

王朝c/c++·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

更深入一点理解 switch 语句 及 c/c++ 对 const 的处理

谢煜波

------------------------------------------------

转载请注明原作者,以出处~~

------------------------------------------------

前段时间在论坛上看见台湾李维在<<Borland传奇>>一书中对windows编程模式中,消息处理部分有如下的一些分析:

他说,在消息处理循环中,一般的形式是这样的

MSG msg ;

switch( msg ){

case WM_XXXXXXX :

....

case WM_XXXXXXX :

....

case WM_XXXXXXX :

....

} ;

李维说,这种模式是很低效的,因应经过汇编后,这种C代码会产生如下的汇编代码

cmp .... .....

jnz .... .....

cmp .... .....

jnz .... .....

cmp .... .....

jnz .... .....

如果你的 case 足够多,比如,你有一万条消息需要处理,而不幸的是你把一条最常用的消息

放在了最后一位,那么当这条消息要得到处理,会首先经过一万次的cmp与jnz, 李维认为,这

是非常非常低效的,实在是低效的忍无可忍,无需再忍~~:P

在起初,我也是这样认为的,但近来的阅读及实验却发现,这种看法非常片面,今天就来谈谈这个问题( 所有实验在 linux 平台下完成 )

首先看一到用 c 编写的程序

/* -------------------- filename : ta.c --------------- */

int switch_test_first( int x )

{

int res ;

switch( x ){

case 100 :

res = 1 ;

break ;

case 102 :

res = 2 ;

break ;

case 103 :

res = 3 ;

break ;

}

return res ;

}

然后,我们用 gcc 将它编译成汇编文件( 使用 -S 开关 )

gcc -S ta.c

将得到如下的汇编文件( ta.s )

.file "ta.c"

.text

.globl switch_test_first

.type switch_test_first,@function

switch_test_first:

pushl %ebp

movl %esp, %ebp

subl $8, %esp

movl 8(%ebp), %eax

.file "ta.c"

.text

.globl switch_test_first

.type switch_test_first,@function

switch_test_first:

pushl %ebp

movl %esp, %ebp

subl $8, %esp

movl 8(%ebp), %eax

movl %eax, -8(%ebp)

cmpl $102, -8(%ebp) // 1

je .L4 // 2

cmpl $102, -8(%ebp) // 3

jg .L8 // 4

cmpl $100, -8(%ebp) // 5

je .L3 // 6

jmp .L2 // 7

.L8:

cmpl $103, -8(%ebp)

je .L5

jmp .L2

.L3:

movl $1, -4(%ebp)

jmp .L2

.L4:

movl $2, -4(%ebp)

jmp .L2

.L5:

movl $3, -4(%ebp)

.L2:

movl -4(%ebp), %eax

leave

ret

.Lfe1:

.size switch_test_first,.Lfe1-switch_test_first

.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

注意看文件中 // 1 ~ // 7 的部份,从这个部份,我们可以看出,gcc确实是把一些case语句转成了李维所说的那种方式进行处理,我们看见了代码中存在有众多的 cmpl 与 jmp 语句

这就相当于你使用if..else..一样,但是否总是这样呢?

我们下面改动一下 ta.c 这个文件,在里面再多加一些 case 语句

/* -------------- filename : new_ta.c ------------------- */

int switch_test_first( int x )

{

int res ;

switch( x ){

case 100 :

res = 1 ;

break ;

case 102 :

res = 2 ;

break ;

case 103 :

res = 3 ;

break ;

case 104 :

res = 4 ;

break ;

case 105 :

res = 5 ;

break ;

case 106 :

res = 6 ;

break ;

}

return res ;

}

这个 new_ta.c 与原来的 ta.c 在结构上完全相同,唯一不同的就是 case 语句的数量变多了,下面我们来编译一下这个文件

gcc -S new_ta.c

下面是我们产生的更新的汇编文件

.file "new_ta.c"

.text

.globl switch_test_first

.type switch_test_first,@function

switch_test_first:

pushl %ebp

movl %esp, %ebp

subl $8, %esp

movl 8(%ebp), %eax

subl $100, %eax

movl %eax, -8(%ebp)

cmpl $6, -8(%ebp)

ja .L2

movl -8(%ebp), %edx

movl .L9(,%edx,4), %eax

jmp *%eax

.section .rodata

.align 4

.align 4

.L9: // A

.long .L3

.long .L2

.long .L4

.long .L5

.long .L6

.long .L7

.long .L8

.text

.L3: // 1

movl $1, -4(%ebp)

jmp .L2

.L4: // 2

movl $2, -4(%ebp)

jmp .L2

.L5: // 3

movl $3, -4(%ebp)

jmp .L2 // 4

.L6:

movl $4, -4(%ebp)

jmp .L2 // 5

.L7:

movl $5, -4(%ebp) // 6

jmp .L2

.L8: // 7

movl $6, -4(%ebp)

.L2:

movl -4(%ebp), %eax

leave

ret

.Lfe1:

.size switch_test_first,.Lfe1-switch_test_first

.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

仔细比较一下这个最新的 new_ta.s 与前面的 ta.s,精华全在里面了!

首先 new_ta.s 比前面的 ta.s 多了一个 .L9 部分,而且它的 // 1 ~ // 7 中没有了前面

ta.s 文件中所存在的众多的 cmpl 与 jmp 语句,那么,现在这样的代码又是怎么实现

switch 语句中的跳转的呢?我们来仔细分析一下它新多出来的 .L9 部份。

.section .rodata

.align 4

.align 4

.L9:

.long .L3

.long .L2

.long .L4

.long .L5

.long .L6

.long .L7

.long .L8

.text

显而易见,.L9 部份是一个我们最常见的数据结构——表,它的每一项都是一个标号,而这个标号,恰恰是每个 case 语句的入口标号!

这很容易让我们想到,它很可能是用了一张表来存放所有的 case 语句的入口,然后,在

执行 switch 语句的时候就从这个表中直接检出相应的 case 语句的入口地址,然后跳转

到相应的 case 语句去执行,就像hash_table似的。具体是不是这样呢?我们看看进入

switch 部份的代码:

pushl %ebp

movl %esp, %ebp

subl $8, %esp

movl 8(%ebp), %eax

subl $100, %eax

movl %eax, -8(%ebp)

cmpl $6, -8(%ebp)

ja .L2

movl -8(%ebp), %edx

movl .L9(,%edx,4), %eax // 1

jmp *%eax // 2

果然如此!首先在 // 1 处根据%edp的值(其值相当于表的下标)在.L9的表中找到相应

case 语句的入口地址,并把这个地址存到%eax中,然后通过 // 2 (这是一个间接跳转

语句)转到%eax存放的地址中,也即相应的case语句处。

C编译器,果然聪明!

通过这个分析我们可以知道如下两点:

1. 当 case 语句少的时候,C编译器将其转成 if..else.. 类型进行处理,运用较多的

cmp 与 jmp 语句 ,而当 case 语句较多的时候,C编译器会出成一个跳转表,而直

接通过跳转表进行跳转,这让 switch 具有非常高的效律,而且效律几乎不会因为

case 语句的增长而减小,李维所担忧的问题是完全不会发生的

2. 可以问答下面几个问题:

1. 为什么 case 语句中需要的是整数类型而不能是其余的类型?

这是因为,case 语句中的这个值是用来做跳转表的下标的,因此,当然必须是整数

2. 为什么 case 语句在不加break的时候具有直通性?

这是因为跳转是在进入 switch 是计算出的,而不是在case语句中计算出的,整个

case 语句群就是一块完整而连续的代码,只是switch让其从不同的位置开始执行。

上面的内容,在《Computer Systems A Programmer's Perspective》中有很详细的论述,

感兴趣可以去找来仔细看看~~~

既然,case 语句需要的是整数的常量值,那么我们是否可用 const 类型呢?比如下面

一段代码:

const int c_1 = 100 ;

const int c_2 = 102 ;

void test( int x )

{

switch( x ){

case c_1 :

++x ;

case c_2 :

--x ;

}

}

这段代码,用 c 编译器编译,编译器会提示错误,但在 c++ 编译器中却不会,这主要是由于 c , 与 c++ 编译器对 const 这个东东的处理不同。我们来看看下面一段 c 程序

/*------------- filename : const_c.c -----------*/

const int a = 15 ;

void f( int x )

{

x = a ;

}

同样用 gcc 编译

gcc -S const_c.c

然后,来看看它的汇编文件

.file "const_c.c"

.globl a

.section .rodata

.align 4

.type a,@object

.size a,4

a: // 1

.long 15

.text

.globl f

.type f,@function

f:

pushl %ebp

movl %esp, %ebp

movl a, %eax // 2

movl %eax, 8(%ebp)

leave

ret

.Lfe1:

.size f,.Lfe1-f

.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

注意 // 1 处,C 编译器为 a 分配了地址,并把它的值设为 15 ,而在 // 2 处,它是将

a 这个地址中的值赋给了 %eax,这同一般的普通变量而非const 变量赋值没什么两样

下面我们用 c++ 编译器来编译这段代码,它产生的汇编文件如下:

.file "const_cpp.cpp"

.text

.align 2

.globl _Z1fi

.type _Z1fi,@function

_Z1fi:

.LFB2:

pushl %ebp

.LCFI0:

movl %esp, %ebp

.LCFI1:

movl $15, 8(%ebp) // 1

leave

ret

.LFE2:

.Lfe1:

.size _Z1fi,.Lfe1-_Z1fi

.section .rodata

.align 4

.type a,@object

.size a,4

a:

.long 15

.ident "GCC: (GNU) 3.2.2 20030222 (Red Hat Linux 3.2.2-5)"

同样注意// 1 处,它以经把 a 的值用 15 来取代了,

也就是说,在c中const变量的行为更像一个非const变量,而在cpp中,const变量的行为就像是#define

由于 c++ 中,const 变量的值是在编译时就计算出来的,因此,它可以用在 case 语句中,而 c 中,const值在编译时只是一个变量的地址,因此,它无法用在 case 语句中.

-----------------------------------------------------------------------------

参考文献:<<Computer Systems A Programmer's Perspective>>

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有