您的位置:首页 > 编程语言 > C语言/C++

重学c语言系列四--变量在内存中的布局(二)

2014-05-22 02:26 218 查看
接着上篇文章,我们大致了解内存被分成几个部分之后,就实际实验一下吧,我拿前面说过的变量文章里的例子代码:

#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,如果数据进行过对齐,那么只需要一个内存周期,就能取到所有数据,内存一般不能从任意位置读取的,

对某些特定类型数据必须从特定地址开始。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: