C++中const常量的存储位置探讨
更新日期:
首先看一段诡异的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#include <stdio.h>
int main()
{
const float intValue=2.2;
float *j=(float *)&intValue;
*j=1.1;
printf("intValue address:0x%x\n",&intValue);
printf("j address:0x%x\n",j);
printf("j:%f\n",*j);
printf("intValue:%f\n",intValue);
return 0;
}
`
输出结果如下:
intValue address:bfd8dddc
j address:bfd8dddc
j:1.100000
intValue:2.2000001
这就纳闷了,为什么地址一样,而所指的值不一样呢~~这就要探讨一下编译器在处理const常量方面的一些机制了,以上代码生成的汇编如下:
.file “constASM.cpp”
.section .rodata //rodata区,此处保存printf中的一些常量
.LC2:
.string “intValue address:0x%x\n”
.LC3:
.string “j address:0x%x\n”
.LC4:
.string “j:%f\n”
.LC6:
.string “intValue:%f\n”
.text
.globl main
.type main, @function
main:
.LFB2:
leal 4(%esp), %ecx //esp:Stack Pointer, 堆栈指针,指向堆栈中即将被操作的那个地址
.LCFI0:
andl $-16, %esp
pushl -4(%ecx)
.LCFI1:
pushl %ebp //ebp可以理解为保存指针的寄存器
.LCFI2:
movl %esp, %ebp
.LCFI3:
pushl %ecx
.LCFI4:
subl $36, %esp //esp自减36,相当于堆栈的大小定为36
.LCFI5:
movl $0x400ccccd, %eax //&intValue操作,会分配内存,并没有引用.LC5,而是直接用立即数赋值
movl %eax, -12(%ebp) //为(ebp-12)地址赋值0x400ccccd,0x400ccccd,即为浮点数2.2
leal -12(%ebp), %eax
movl %eax, -8(%ebp) //此时(ebp-8)地址赋值为0x400ccccd
movl -8(%ebp), %edx //将(ebp-8)地址存入edx
movl $0x3f8ccccd, %eax
movl %eax, (%edx) //为(ebp-8)地址赋值为0x3f8ccccd,即浮点数1.1
leal -12(%ebp), %eax
movl %eax, 4(%esp) //将(ebp-12)地址的值读入(4+esp)地址为printf做准备
movl $.LC2, (%esp) //将.LC2的地址读入(esp)地址,为printf做准备
call printf //printf(“intValue address:0x%x\n”,&intValue);
movl -8(%ebp), %eax
movl %eax, 4(%esp)
movl $.LC3, (%esp)
call printf //printf(“j address:0x%x\n”,j);
movl -8(%ebp), %eax
flds (%eax) //浮点数压栈,压入的是(ebp-8)地址的值
fstpl 4(%esp)
movl $.LC4, (%esp)
call printf //printf(“j:%f\n”,*j);
fldl .LC5 //浮点数压栈,压入的是LC5,这里就是区别!!!直接从符号表中找到intValue,跟前面的内存操作无关!!
fstpl 4(%esp) //
movl $.LC6, (%esp)
call printf //printf(“intValue:%f\n”,intValue);
movl $0, %eax
addl $36, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.LFE2:
.size main, .-main
.section .rodata
.align 8
.LC5:
.long -1610612736
.long 1073846681
.section .eh_frame,”a”,@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0x0
.byte 0x1
.
.//此处省略一部分
.
.LEFDE1:
.ident “GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)”
.section .note.GNU-stack,””,@progbits1
从以上分析可以得到以上结论:
1.对const常量取地址时,编译器会进行内存分配,并将常量转换为立即数存入内存,而不是存入记录在常量表中的地址
2.在使用常量时,编译器回到常量表中查询对应的常量,并将其替换,这部分没有涉及内存分配,也跟曾经创建的常量的内存地址无关。
___
用const_case<>将一个const常量的地址转换后扶植给一个普通指针,然后通过这个指针赋值,为什么原来的值并不改变?而这个新的值又保存在什么地方?
比如,
const int i = 1;
int p = const_cast<int>(&i);
*p = 2;
cout<<i<<endl; // 仍然是1,为什么?
cout<<*p<<endl; // 输出为2,这个2保存在什么地方?
```
i因为声明成const,在同一个module里编译时是直接作为常量,相当于你写 cout << 1 << endl。你如果看编译器输出的汇编代码,就会看到这个1是以立即数的方式存在于指令中,而不是从某个内存地址读取。
如果你的代码里只引用了i,而没有写 &i 去取i的地址,编译器甚至可能根本不为i分配任何实际的内存空间,但一旦写了&i,那就一定有实际分配的空间,虽然在引用i的时候根本不会读这块内存。
类似的,函数里的最常用到的局部变量一般都会被优化为使用寄存器来存储,不会分配内存,除非你用了 &来求局部变量的地址——与const不同的是,这个变量就不会再使用寄存器存储,而是一直从内存访问。