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

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

首先,引用一道题的代码和“答案”,这是“比較堆和栈存取效率”的。可是其实,他给的两种方式都用的栈,个人试出来的占用堆空间的情况,仅仅能是malloc()和new()等系统调用产生的。

#include<stdio.h>

main(){

char a = 1;

char c[] = "1234567890";

char *p = "1234567890";

a = c[1];

a = p[1];

}

“答案”:存取效率的比較

chars1[]="aaaaaaaaaaaaaaa";

char *s2="bbbbbbbbbbbbbbbbb";

aaaaaaaaaaa是在执行时刻赋值的;

而bbbbbbbbbbb是在编译时就确定的。

主要疑问是,两者都在栈中存储,确定"aaa...."是执行时刻赋值的么?栈中是执行时“赋值”么。

那么,既然看到了这段代码。还是对照一下吧。能够先比較一下以“数组”和"指针"(后边会解释详细含义)形式初始化的两段字符串的存取效率。

宏汇编指令运行过程:

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

4       char a = 1;

1: x/i $pc

=> 0x8048419 <main+21>:    movb   $0x1,0x10(%esp)

5        char c[] = "1234567890";

0x804841e <main+26>:   movl   $0x34333231,0x11(%esp)

0x8048426 <main+34>:   movl   $0x38373635,0x15(%esp)

0x804842e <main+42>:   movw   $0x3039,0x19(%esp)

0x8048435 <main+49>:   movb   $0x0,0x1b(%esp)

6        char *p = "1234567890";

0x804843a <main+54>:  movl   $0x8048540,0xc(%esp)

7        a = c[1];

0x8048442 <main+62>:  movzbl 0x12(%esp),%eax

0x8048447 <main+67>:   mov    %al,0x10(%esp)

8        a = p[1];

0x804844b <main+71>: mov 0xc(%esp),%eax

0x804844f <main+75>: movzbl 0x1(%eax),%eax

0x8048453 <main+79>: mov %al,0x10(%esp)

10    }

0x8048457 <main+83>: mov 0x1c(%esp),%edx

0x804845b <main+87>: xor %gs:0x14,%edx

0x8048462 <main+94>: je

0x8048469 <main+101>

0x8048464 <main+96>: call

0x8048320 <__stack_chk_fail@plt>

0x8048469 <main+101>: leave

0x804846a <main+102>: ret

(依据变量声明的先后顺序能够看到。在linux栈偏移地址是增长的)

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

char c[] = "1234567890";

0x804841e <main+26>:   movl   $0x34333231,0x11(%esp)

0x8048426 <main+34>:   movl   $0x38373635,0x15(%esp)

0x804842e <main+42>:   movw   $0x3039,0x19(%esp)

0x8048435 <main+49>:   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

PS:虽然第三次仅仅压入word长的数据(两字节),但还是单独用了一行指令压大小为byte的结束符。

6        char *p = "1234567890";

0x804843a <main+54>:  movl   $0x8048540,0xc(%esp)

p指针本身肯定在栈,直接让p指针指向字符串常量的地址(0x8048540),“1234567890”被存入该地址的过程被省略了,自己主动的

7           a = c[1];

0x8048442 <main+62>:  movzbl 0x12(%esp),%eax

0x8048447 <main+67>:   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 <main+71>: mov 0xc(%esp),%eax

0x804844f <main+75>: movzbl 0x1(%eax),%eax

0x8048453 <main+79>: mov %al,0x10(%esp)

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

第二句较难:

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

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

赋值完毕

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

能够看到的是,两个字符串在读取的时候相同是用数组下标的操作形式,所以和操作方式无关?

感觉不够严谨,也測试了用指针的操作形式,例如以下(多次測试。和上边过程变量地址恐有变化。原理同样就可以):

11        a = *(c + 1);

1: x/i $pc

=> 0x8048487 <main+83>:    lea    0x21(%esp),%eax

(gdb)

0x0804848b    11        a = *(c + 1);

1: x/i $pc

=> 0x804848b <main+87>:    movzbl 0x1(%eax),%eax

(gdb)

0x0804848f    11        a = *(c + 1);

1: x/i $pc

=> 0x804848f <main+91>:    mov    %al,0x20(%esp)

取出C的地址

取出——C的地址+1偏移量所指向的——值

将该值传递给变量a

(gdb)

12        a = *(p + 1);

1: x/i $pc

=> 0x8048493 <main+95>:    mov    0x1c(%esp),%eax

(gdb)

0x08048497    12        a = *(p + 1);

1: x/i $pc

=> 0x8048497 <main+99>:    movzbl 0x1(%eax),%eax

(gdb)

0x0804849b    12        a = *(p + 1);

1: x/i $pc

=> 0x804849b <main+103>:    mov    %al,0x20(%esp)

取出p指向的“字符串常量”的首地址

取出——p指向的“字符串常量”的首地址+1偏移量所指向的——值

将该值传递给变量a

两者唯一差别就是指令lea和mov,原因就是p指向的是“常量区”,仅仅须要p的内容(即目标地址)就可以,而c。要取自身的地址。

PS:没有对照堆空间的存取问题,由于涉及系统调用。指令许多。过程很慢。堆比栈存取慢许多是显然的了

附:

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

=> 0x8048456 <main+34>: 
movl   $0x38373635,0x25(%esp)

(gdb) print $esp

$2 = (void *) 0xbffff230

(gdb) si

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

=> 0x804845e <main+42>: 
movw   $0x3039,0x29(%esp)

(gdb) print $esp

$3 = (void *) 0xbffff230

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

=> 0x8048465 <main+49>: 
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 (from)byte (to)word;movswl意味着movs (from)word (to)long。

对于movz指令也一样。比方指令“movsbl   %al, %edx”意味着将al寄存器的内容进行符号扩展后放置到edx寄存器中。

movzx是将源操作数的内容复制到目的操作数。并将该值0扩展至16位或者32位。

可是它仅仅适用于无符号整数。

他大致分为以下的三种格式:

movzx 32位通用寄存器,8位通用寄存器/内存单元

movzx 32位通用寄存器,16位通用寄存器/内存单元

movzx 16位通用寄存器, 8位通用寄存器/内存单元

堆空间是程序执行时动态申请的,系统维护一个关于空暇区域的链表,从小到大按容量找。找到第一个符合要求(大于等于所需空间)的结点,分配之。

那么也不一定就全用链表,在WINDOWS下,最好的方式是用VirtualAlloc分配内存。他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,尽管用起来最不方便。可是速度快,也最灵活。

那么删除怎么删?怎么知道删多少?这个大小是系统记录的。不是问题,仅仅管free()、delete()就成了。

假设申请的少,不巧没有非常合适的,分配多了的部分,系统还会释放掉,免得浪费。

Linux C存取效率对照——堆、栈、常量区的更多相关文章

  1. Java堆/栈/常量池以及String的详细详解(转)------经典易懂系统

    一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据 ...

  2. Java 底层机制(JVM/堆/栈/方法区/GC/类加载)

    转载:https://www.jianshu.com/p/ae97b692614e?from=timeline JVM体系结构 JVM是一种解释执行class文件的规范技术.   JVM体系结构 我翻 ...

  3. java 堆 栈 常量池

    java 堆中保存new 出来的对象(每个对象都包含一个与之对应的class的信息,[class信息存放在方法区]),堆中分配的内存,有虚拟机的自动垃圾回收器管理,栈内存只对其所属线程可见. java ...

  4. java 堆 栈 方法区的简单分析

    Java里的堆(heap)栈(stack)和方法区(method) 基础数据类型直接在栈空间分配, 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收.   引用数据类型,需要用new来创 ...

  5. JVM堆 栈 方法区详解

    一.栈 每当启用一个线程时,JVM就为他分配一个JAVA栈,栈是以帧为单位保存当前线程的运行状态 栈是由栈帧组成,每当线程调用一个java方法时,JVM就会在该线程对应的栈中压入一个帧 只有在调用一个 ...

  6. Java中 堆 栈,常量池等概念解析(转载)

    1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符 ...

  7. 从内存的角度观察 堆、栈、全局区(静态区)(static)、文字常量区、程序代码区

    之前写了一篇堆栈的,这里再补充下内存其他的区域 1.栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 2.堆区(heap) — 一般由程 ...

  8. c语言知识点总结-------静态区、堆、栈、常量区等

    在C语言中地址占4个字节 1.编程语言发展 低级语言----->高级语言 机器语言 ---> 汇编---->高级语言(C语言.C++.JAVA等) 机器语言 :0101 0010 1 ...

  9. C++中栈区 堆区 常量区

    原文地址:http://blog.csdn.net/xcyuzhen/article/details/4543264 C++中栈区 堆区 常量区(由一道面试题目而学习) -- : #include&l ...

随机推荐

  1. 2017 多校6 String

    多校6 String(ac自动机) 题意: 给一本有\(n\)个单词的字典 \(q\)个查询 \(pref_i,suff_i\) 查询字典里有多少单词前缀匹配\(pref_i\),后缀同时匹配\(su ...

  2. BZOJ1023[SHOI2008]cactus仙人掌图 【仙人掌DP】

    题目 如果某个无向连通图的任意一条边至多只出现在一条简单回路(simple cycle)里,我们就称这张图为仙人掌 图(cactus).所谓简单回路就是指在图上不重复经过任何一个顶点的回路. 举例来说 ...

  3. 《c程序设计语言》读书笔记-4.14-定义宏交换两个参数

    #include <stdio.h> #include <math.h> #include <stdlib.h> #include <string.h> ...

  4. android工程下assets与raw文件夹

    在应用的开发中,当我们创建一个新的Android工程后,我们会发现工程中包含了一个目录assets,另外当我们察看一些示例工程的时候,有时会发现在该工程的资源目录下会有一个raw目录(res/raw) ...

  5. java 复习整理(二 数据类型和几种变量)

    源文件声明规则 当在一个源文件中定义多个类,并且还有import语句和package语句时,要特别注意这些规则. 一个源文件中只能有一个public类 一个源文件可以有多个非public类 源文件的名 ...

  6. Topcoder SRM 603 div1题解

    昨天刚打了一场codeforces...困死了...不过赶在睡前终于做完了- 话说这好像是我第一次做250-500-1000的标配耶--- Easy(250pts): 题目大意:有一棵树,一共n个节点 ...

  7. javascript jquery document.ready window.onload

    网易 博客 下载LOFTER客户端 注册登录  加关注 凡图的编程之路 2012年7月从一个编程新手的点点滴滴 首页 日志 LOFTER 相册 博友 关于我     日志       关于我 Holy ...

  8. Flash Builder 4.7 完美破解

    1. 准备安装文件和序列号生成器1Adobe Flash Builder 4.7 的安装文件可以从以下两个连接下载到:•32bit:http://trials3.adobe.com/AdobeProd ...

  9. Linux下的GitHub安装与简单配置教程 ~ 转载

    Linux下的GitHub安装与简单配置教程   1.GitHub简介 Git是一个分布式版本控制系统,与其相对的是CVS.SVN等集中式的版本控制系统. 2.Git的安装 1)安装Git a.查看与 ...

  10. 放棋游戏(NOIP模拟赛)(DP)

    没有原题... 囧.. [问题描述] 游戏规则是这样,有n(1<=n<=100)行格子,第一行由n个格子,第二行有n-1个格子,第三行由n-2个格子,……以此类推,第n行有1个格子.要求再 ...