Linux C存取效率对照——堆、栈、常量区
本文主要探讨堆和栈在使用中的存取效率。利用宏汇编指令分析訪存情况来进行简单推断。
实验环境及使用工具: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存取效率对照——堆、栈、常量区的更多相关文章
- Java堆/栈/常量池以及String的详细详解(转)------经典易懂系统
一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据 ...
- Java 底层机制(JVM/堆/栈/方法区/GC/类加载)
转载:https://www.jianshu.com/p/ae97b692614e?from=timeline JVM体系结构 JVM是一种解释执行class文件的规范技术. JVM体系结构 我翻 ...
- java 堆 栈 常量池
java 堆中保存new 出来的对象(每个对象都包含一个与之对应的class的信息,[class信息存放在方法区]),堆中分配的内存,有虚拟机的自动垃圾回收器管理,栈内存只对其所属线程可见. java ...
- java 堆 栈 方法区的简单分析
Java里的堆(heap)栈(stack)和方法区(method) 基础数据类型直接在栈空间分配, 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收. 引用数据类型,需要用new来创 ...
- JVM堆 栈 方法区详解
一.栈 每当启用一个线程时,JVM就为他分配一个JAVA栈,栈是以帧为单位保存当前线程的运行状态 栈是由栈帧组成,每当线程调用一个java方法时,JVM就会在该线程对应的栈中压入一个帧 只有在调用一个 ...
- Java中 堆 栈,常量池等概念解析(转载)
1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符 ...
- 从内存的角度观察 堆、栈、全局区(静态区)(static)、文字常量区、程序代码区
之前写了一篇堆栈的,这里再补充下内存其他的区域 1.栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈. 2.堆区(heap) — 一般由程 ...
- c语言知识点总结-------静态区、堆、栈、常量区等
在C语言中地址占4个字节 1.编程语言发展 低级语言----->高级语言 机器语言 ---> 汇编---->高级语言(C语言.C++.JAVA等) 机器语言 :0101 0010 1 ...
- C++中栈区 堆区 常量区
原文地址:http://blog.csdn.net/xcyuzhen/article/details/4543264 C++中栈区 堆区 常量区(由一道面试题目而学习) -- : #include&l ...
随机推荐
- poj 1390 区间dp
Blocks Time Limit: 5000MS Memory Limit: 65536K Total Submissions: 5035 Accepted: 2065 Descriptio ...
- Linux 软连接 & 硬链接
1.Linux链接概念Linux链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link).默认情况下,ln命令产生硬链接. [硬连接]硬连接指通过索引节点 ...
- 记录string的妙用
P1106 删数问题 摘要 --> 题目描述 键盘输入一个高精度的正整数N,去掉其中任意k个数字后剩下的数字按原左右次序将组成一个新的正整数.编程对给定的N和k,寻找一种方案使得剩下的数字组成的 ...
- Log4Net日志分类和自动维护
背景 在程序中,我们调试运行时信息,Log4Net是一个不错的解决方案.不知道是我用的不好,用到最后反而都不想看日志了.原因是因为我n个功能使用的默认的Logger来记录日志,这样以来,所有功能记录的 ...
- 在iOS上实现一个简单的日历控件
http://blog.csdn.net/jasonblog/article/details/21977481 近期需要写一个交互有点DT的日历控件,具体交互细节这里略过不表. 不过再怎么复杂的控件, ...
- Flex与51单片机socket通信 策略问题
直接把<cross-domain-policy> <allow-access-from domain="*" to-ports="*"/> ...
- Python 如何调用 Java
引用了这个文章,请打开链接 http://www.cnblogs.com/junrong624/p/5278457.html 日后待补写 ...
- WCF使用小例子
using System.Runtime.Serialization; using System.ServiceModel; using MySpace; using System.ServiceMo ...
- 【linux高级程序设计】(第十二章)Linux多线程编程 4
读写锁 书上有读者写者的代码,我实在是懒得实现一遍了.跟之前的代码差不多. 多线程异步信号处理 int pthread_kill (pthread_t __threadid, int __signo) ...
- python3正则表达式符号和用法