const机制详解
博客来自于对下面这个问题的探索。
下面这条语句可以编译通过吗?
const int* a;
答案 可以的,因为它只是一个指向“常量”的指针,充其量是一个野指针
这个问题很简单,但是却引发了一系列的问题。大家可能想到的是指针常量和常量指针的问题,这个问题却不是重点了,通过一句话就可以解释就近原则,const 修饰最近的一个类型,const int* const修饰int 就是指向一个”常量“的指针,int* const const自然修饰==int*==它就是一个指针常量了。
那么const的机制是什么呢?
我做了如下测试:
测试1
#include <stdio.h>
const int val = 10;
int main()
{
int* tmp_val = const_cast<int*>(&val); //==>int* ptr_a = (int*)&a;
*tmp_val = 20;
printf("%d\n",val);
return 0;
}
运行结果:
Segmentation fault
测试2
#include <stdio.h>
int main()
{
const int val = 10; //不同点 区部变量
int* tmp_val = const_cast<int*>(&val); //==>int* ptr_a = (int*)&a;
*tmp_val = 20;
printf("*tmp_val = %d,val = %d\n",*tmp_val,val);
return 0;
}
运行结果:
*tmp_val = 20,val = 10
emm…
测试3
#include <stdio.h>
int main()
{
const int val = 10;
int* tmp_val = const_cast<int*>(&val); //==>int* ptr_a = (int*)&a;
*tmp_val = 20;
printf("tmp_val = %p,&val = %p\n",tmp_val,&val);
printf("*tmp_val = %d,val = %d\n",*tmp_val,val);
return 0;
}
运行结果:
tmp_val = 0x7ffe8603b8b4,&val = 0x7ffe8603b8b4
*tmp_val = 20,val = 10
what? 同一个地址两个不同的值
测试4
#include <stdio.h>
int main()
{
const int val = 10;
int test = val;
return 0;
}
看一下对应的汇编
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $10, -4(%rbp)
movl $10, -8(%rbp) //看这里,它并没有去取val里面的值而是直接将10赋值给了test
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
结论:
问题1:what? 同一个地址两个不同的值
答案:通过测试2,3, 4 可以看出,我的编译器(可能你的编译器就不优化了)对其做了优化,直接替换val,类似于宏定义
延伸:这个优化只对内置类型有效。
演示:
#include <stdio.h>
struct Int
{
int val;
Int(){this->val = 10;}
};
int main()
{
const Int a;
printf("修改前:a.val = %d\n",a.val);
Int* ptr_a = const_cast<Int*>(&a); // ==> int* ptr_a = (int*)&a;
ptr_a->val = 20;
printf("修改后:a.val = %d\n",a.val);
return 0;
}
运行结果:
修改前:a.val = 10
修改后:a.val = 20
解释测试1,2
测试1和2区别在于,const int val = 10; 一个是全局变量一个是局部变量。
全局变量,const int val =10; 会被分配到 .rodata 只读存储区,在运行时如果读取了只读存储区,就会出发段错误,读取了不该读取的位置。
局部变量,是放在stack里的,在栈中没有只读属性。所以这里只能通过编译器做编译时检查。那么编译器能力有限,管得了编译,管不了运行,所以可以通过指针绕过const
|