重学c语言系列四--变量在内存中的布局(二)
2014-05-22 02:26
218 查看
接着上篇文章,我们大致了解内存被分成几个部分之后,就实际实验一下吧,我拿前面说过的变量文章里的例子代码:
首先,不急于看可执行文件的结果,我们到汇编层面看一下,我们写的要被编译器怎么处理,处理成什么样?
说明:因为要学习ios开发,目前的环境是mac 10.9,采用otool对可执行文件进行反汇编的,在linux下可以用objdump达到类似的效果,为什么要反汇编?直接gcc生成的汇编代码实在是太难以阅读了....
结果如下:这里先列出来的是代码段
命令:otool -tV <可执行文件> > ans 参数t表示text段,> ans 是把结果重定向到ans的文本文件中.
这是AT & T风格的汇编,和很早在组成原理课上学的x86 汇编有一些区别,不过对我来说都是一样(那些语法早就忘记了)
不过慢慢研究,还是会弄明白的,为了不一开始就看着头晕,我们要弄明白一些符号的意思
%号开头的一搬表示的都是一个寄存器,exx表示是32位寄存器,rxx表示64位寄存器,$开始表示一个立即数,0x表示16进制....
第一行是 pushq %rbp, push是一个压栈指令,把rbp寄存器的值压入栈中,bp寄存器里保存的是帧指针,看下图:
图片来源:某博客
上一篇提到的内存中栈那部分是由一块块栈帧构成的,栈帧大小不固定,而第一句指令是把之前的的帧指针压入栈,为了我们写的程序完成后能够继续之前的程序
movq %rsp, %rbp, 注意和x86汇编不同的是,指令第一个操作数是源操作数,第二个数是目标操作数。sp是栈指针,是会动态变化的,我们push之后,sp的值就变化了
而bp一般是不变的,把sp的值给bp,是建立新的帧(函数帧)
第3行,subq $0x20, %rsp, 因为栈的生成方向从高地址到低地址,sp向下偏移32个字节,是为函数分配局部变量,而main中的局部变量,只有几个int,很明显不需要32个字节
这么大,这是为了内存对齐,我们知道对齐一般都是2的多少次方,虽然内存中是一个字节一个字节储存,但是将数据从内存转移到其它地方,却是一块一块(因为这样有效率),提高了转移速度,现在的cpu也是一次运算多个字节,提高运算速度和精度,我们所说的32位或者64位机器,指的就是字长,是cpu一次处理的数据的长度,32位就是4个字节,64位就是8个字节,通常也是一般寄存器的保存数据长度和数据总线的位数相等。
我本人的操作系统式64位,但这里是偏移了32位,不能说就是按照32位来对齐,我减少定义的数据数量,最后main什么局部变量都没有,还是偏移了16,那就是按照16字节来对齐的。
然后,为什么需要内存对齐,假设一个int存放在 0xFFFFFF01~0xFFFFF04四个字节,第一次读取0xFFFFFF00-0xFFFFFF03四个字节,取后3个字节,然后读0xFFFFFF04-0xFFFFFF07,取第一个字节,两次的数据进行合并,才能取到一个int,如果数据进行过对齐,那么只需要一个内存周期,就能取到所有数据,内存一般不能从任意位置读取的,
对某些特定类型数据必须从特定地址开始。
#include<stdio.h> #define YES 1 //extern int extern_var ; extern reall_extern_var ; int golbal_var=3; int golbao_var2; int main() { //extern int extern_var ; printf("extern var is:%d and its address is:%x\n",reall_extern_var,&reall_extern_var); int auto_x=15,i=10; //printf("%d, %d\n",&YES); printf("%x\n",&auto_x); static int static_var; printf("%x\n",&static_var); register int reg_var=0; for(i=0; i<10; ++i){ reg_var++; // printf("%d",®_var); 寄存器变量没有地址 } return 0; }不同的是,这次在代码中用printf语句输出一些变量的地址,便于我们分析印证
首先,不急于看可执行文件的结果,我们到汇编层面看一下,我们写的要被编译器怎么处理,处理成什么样?
说明:因为要学习ios开发,目前的环境是mac 10.9,采用otool对可执行文件进行反汇编的,在linux下可以用objdump达到类似的效果,为什么要反汇编?直接gcc生成的汇编代码实在是太难以阅读了....
结果如下:这里先列出来的是代码段
命令:otool -tV <可执行文件> > ans 参数t表示text段,> ans 是把结果重定向到ans的文本文件中.
relearn: (__TEXT,__text) section _main: 0000000100000ea0 pushq %rbp 0000000100000ea1 movq %rsp, %rbp 0000000100000ea4 subq $0x20, %rsp 0000000100000ea8 leaq 0xbb(%rip), %rdi ## literal pool for: "extern var is:%d and its address is:%x " 0000000100000eaf leaq _reall_extern_var(%rip), %rax 0000000100000eb6 movl $0x0, -0x4(%rbp) 0000000100000ebd movl (%rax), %esi 0000000100000ebf movq %rax, %rdx 0000000100000ec2 movb $0x0, %al 0000000100000ec4 callq 0x100000f48 ## symbol stub for: _printf 0000000100000ec9 leaq 0xc2(%rip), %rdi ## literal pool for: "%x " 0000000100000ed0 leaq -0x8(%rbp), %rsi 0000000100000ed4 movl $0xf, -0x8(%rbp) 0000000100000edb movl $0xa, -0xc(%rbp) 0000000100000ee2 movl %eax, -0x14(%rbp) 0000000100000ee5 movb $0x0, %al 0000000100000ee7 callq 0x100000f48 ## symbol stub for: _printf 0000000100000eec leaq 0x9f(%rip), %rdi ## literal pool for: "%x " 0000000100000ef3 leaq _main.static_var(%rip), %rsi 0000000100000efa movl %eax, -0x18(%rbp) 0000000100000efd movb $0x0, %al 0000000100000eff callq 0x100000f48 ## symbol stub for: _printf 0000000100000f04 movl $0x0, -0x10(%rbp) 0000000100000f0b movl $0x0, -0xc(%rbp) 0000000100000f12 movl %eax, -0x1c(%rbp) 0000000100000f15 cmpl $0xa, -0xc(%rbp) 0000000100000f1c jge 0x100000f3d 0000000100000f22 movl -0x10(%rbp), %eax 0000000100000f25 addl $0x1, %eax 0000000100000f2a movl %eax, -0x10(%rbp) 0000000100000f2d movl -0xc(%rbp), %eax 0000000100000f30 addl $0x1, %eax 0000000100000f35 movl %eax, -0xc(%rbp) 0000000100000f38 jmpq 0x100000f15 0000000100000f3d movl $0x0, %eax 0000000100000f42 addq $0x20, %rsp 0000000100000f46 popq %rbp 0000000100000f47 ret
这是AT & T风格的汇编,和很早在组成原理课上学的x86 汇编有一些区别,不过对我来说都是一样(那些语法早就忘记了)
不过慢慢研究,还是会弄明白的,为了不一开始就看着头晕,我们要弄明白一些符号的意思
%号开头的一搬表示的都是一个寄存器,exx表示是32位寄存器,rxx表示64位寄存器,$开始表示一个立即数,0x表示16进制....
第一行是 pushq %rbp, push是一个压栈指令,把rbp寄存器的值压入栈中,bp寄存器里保存的是帧指针,看下图:
图片来源:某博客
上一篇提到的内存中栈那部分是由一块块栈帧构成的,栈帧大小不固定,而第一句指令是把之前的的帧指针压入栈,为了我们写的程序完成后能够继续之前的程序
movq %rsp, %rbp, 注意和x86汇编不同的是,指令第一个操作数是源操作数,第二个数是目标操作数。sp是栈指针,是会动态变化的,我们push之后,sp的值就变化了
而bp一般是不变的,把sp的值给bp,是建立新的帧(函数帧)
第3行,subq $0x20, %rsp, 因为栈的生成方向从高地址到低地址,sp向下偏移32个字节,是为函数分配局部变量,而main中的局部变量,只有几个int,很明显不需要32个字节
这么大,这是为了内存对齐,我们知道对齐一般都是2的多少次方,虽然内存中是一个字节一个字节储存,但是将数据从内存转移到其它地方,却是一块一块(因为这样有效率),提高了转移速度,现在的cpu也是一次运算多个字节,提高运算速度和精度,我们所说的32位或者64位机器,指的就是字长,是cpu一次处理的数据的长度,32位就是4个字节,64位就是8个字节,通常也是一般寄存器的保存数据长度和数据总线的位数相等。
我本人的操作系统式64位,但这里是偏移了32位,不能说就是按照32位来对齐,我减少定义的数据数量,最后main什么局部变量都没有,还是偏移了16,那就是按照16字节来对齐的。
然后,为什么需要内存对齐,假设一个int存放在 0xFFFFFF01~0xFFFFF04四个字节,第一次读取0xFFFFFF00-0xFFFFFF03四个字节,取后3个字节,然后读0xFFFFFF04-0xFFFFFF07,取第一个字节,两次的数据进行合并,才能取到一个int,如果数据进行过对齐,那么只需要一个内存周期,就能取到所有数据,内存一般不能从任意位置读取的,
对某些特定类型数据必须从特定地址开始。
相关文章推荐
- 重学c语言系列三---变量在内存中的布局
- 我也要学C语言-第六章:变量在内存中的地址与布局
- C语言变量存储区域 进程内存布局
- C语言编程程序的内存如何布局
- float和double变量的内存布局~~~~~~
- C++内存布局--从一个修改私有变量的问题想到的
- 不管结构变量在栈还是堆中分配。结构中成员的内存布局跟定义的是一样的
- [导入]CLR探索系列:System.Object内存布局模型及实现研究
- c语言内存布局详解
- C++程序运行时内存布局之----------局部变量,全局变量,静态变量,函数代码,new出来的变量
- C++程序运行时内存布局之----------简单类实例,成员变量,成员函数,静态成员变量,静态成员函数
- CLR探索系列:System.Object内存布局模型及实现研究 转
- 【我解C语言面试题系列】002 局部变量和全局变量小结?
- C语言中的全局变量内存分配和初始化顺序
- C语言变量声明内存分配
- [原创]探索CLR原理系列(2):字段在内存中的布局 (适合老鸟,新人勿沉迷其中)
- CLR探索系列:System.Object内存布局模型及实现研究
- CLR探索系列:System.Object内存布局模型及实现研究
- c、c++在定义变量,数组时的内存布局及内存字节对齐
- c语言 输出变量的地址,动态的观察内存的分配。