设为首页 加入收藏

TOP

C程序堆、栈存取效率对比(一)
2015-01-22 21:37:36 来源: 作者: 【 】 浏览:171
Tags:程序 存取 效率 对比

本文主要探讨堆和栈在使用中的存取效率,利用宏汇编指令分析访存情况来进行简单判断。

实验环境及使用工具:i686,32位Ubuntu Linux,gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3,gdb


看一小段代码:

#include

main(){

char a = 1;

char c[] = "1234567890";

char *p = "1234567890";

a = c[1];

a = p[1];

}

char s1[]="hello";
char *s2="world";
s1指向的字符串属于栈,s2指向的字符串属于堆。(他们本身当然都属于栈)

栈地址空间的分配是编译链接时,而堆中的地址空间是运行时动态申请和分配的。

在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。


PS:堆空间是程序运行时动态申请的,系统维护一个关于空闲区域的链表,从小到大按容量找,找到第一个符合要求(大于等于所需空间)的结点,分配之。那么删除怎么删?怎么知道删多少?这个大小是系统记录的,不是问题,只管free()、delete()就成了。如果申请的少,不巧没有很合适的,分配多了的部分,系统还会释放掉,免得浪费。




宏汇编指令执行过程:

Breakpoint 1, main () at efficiencyOfStorage.c:4

4 char a = 1;

1: x/i $pc

=> 0x8048419 : movb $0x1,0x10(%esp)

5 char c[] = "1234567890";

0x804841e : movl $0x34333231,0x11(%esp)

0x8048426 : movl $0x38373635,0x15(%esp)

0x804842e : movw $0x3039,0x19(%esp)

0x8048435 : movb $0x0,0x1b(%esp)

6 char *p = "1234567890";

0x804843a : movl $0x8048540,0xc(%esp)

7 a = c[1];

0x8048442 : movzbl 0x12(%esp),%eax

0x8048447 : mov %al,0x10(%esp)

8 a = p[1];

0x804844b : mov 0xc(%esp),%eax

0x804844f : movzbl 0x1(%eax),%eax

0x8048453 : mov %al,0x10(%esp)

10 }

0x8048457 : mov 0x1c(%esp),%edx

0x804845b : xor %gs:0x14,%edx

0x8048462 : je

0x8048469

0x8048464 : call

0x8048320 <__stack_chk_fail@plt>

0x8048469 : leave

0x804846a : ret

(根据变量声明的先后顺序可以看到,在linux栈偏移地址是增长的)

首先,它是字符数组,数字字符0-9转换成ascii码是0x30-0x39。


char c[] = "1234567890";

0x804841e : movl $0x34333231,0x11(%esp)

0x8048426 : movl $0x38373635,0x15(%esp)

0x804842e : movw $0x3039,0x19(%esp)

0x8048435 : movb $0x0,0x1b(%esp)

整个数组c包括结束符应该占用11个地址空间(可以用sizeof验证),为0x11至0x1b。

小端模式,字符数组“01234567890” 从低地址0x11开始排列,到0x1b结束(结束符ascii值0x00):

栈中偏移地址:0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b

相应内存内容:0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x00

6 char *p = "1234567890";

0x804843a : movl $0x8048540,0xc(%esp)

p指针直接存到栈,“1234567890”被存入堆的过程省略了。但是这个过程应该比存到栈中慢,栈中地址空间是编译链接时决定的,而堆是运行时。

7 a = c[1];

0x8048442 : movzbl 0x12(%esp),%eax

0x8048447 : mov %al,0x10(%esp)

从地址0x12取出值0x32,传给eax寄存器。


关于movzbl,文章底部有详解,说通俗点就是把(8位)byte长度的值0x32移到(32位)long长度的某地址存储空间中(此例为eax)寄存器了――此时eax中值0x00000032(前24位应该补0,因为“zero”,可以肯定后八位是0x32,就行了)

mov al把eax的低8位值0x32,即数字2,存到栈偏移地址0x10(即变量a的地址)。赋值完成

如果这些简单汇编看不懂,还感兴趣,请移步我的通俗的汇编贴


8 a = p[1];

0x804844b : mov 0xc(%esp),%eax

0x804844f : movzbl 0x1(%eax),%eax

0x8048453 : mov %al,0x10(%esp)

将栈偏移地址0xc中储存的指针p(内容为指向的堆的地址)移到eax寄存器中。

第二句较难:

从eax中取出指针,偏移1,读取字符串中第二个字符’2’,把该(八位)地址对应的值(0x32,即数字2)存到栈偏移地址0x10(即变量a的地址)。

将eax寄存器中低8位,即0x32,传给栈偏移地址0x10中,即为给a赋值。

赋值完成



结论:可以明显看出,前者直接有目的地从栈中读取数据到寄存器eax中,后者则要先把指针值读出来,再通过指针去找需要的地址的值,根据我们关于计算机组成原理的常识,多了一次访问内存,显然效率低了。

附:

文中所谓“栈偏移地址0x10”之类,非绝对地址,皆指偏移地址,%esp是一个固定位置,偏移多少就是固定位置加多少偏移量。

=> 0x8048456 : movl $0x38373635,0x25(%esp)

(gdb) print $esp

$2 = (void *) 0xbffff230

(gdb) si

0x0804845e 5 char c[] = "1234567890";

=> 0x804845e : movw $0x3039,0x29(%esp)

(gdb) print $esp

$3 = (void *) 0xbffff230

0x08048465 5 char c[] = "1234567890";

=> 0x8048465 : movb $0x0,0x2b(%esp)

(gdb) print $esp

$4 = (void *) 0xbffff230

movzbl:

在AT&T语法中,符号扩展和零扩展指令的格式为,基本部分"movs"和"movz"(对应Intel语法的为movsx和movzx,movzx为零扩展,即高位补零,movsx为符号扩展,即高位补符号位)

后面跟上源操作数长度和目的操作数长度。movsbl意味着movs (from)byte (to)long;movbw意味着movs (f

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C语言可变长参数实现原理 下一篇C语言程序中为什么要使用debug宏?

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: