内存中的static、const实现形式
最近在考虑下半年找工作的事情,看了不少面试题目,其中还是蛮有收获的,把基础好好复习了一遍。比如这个题目,static、const现形式,static和const类型的变量在写程序的时候也写了很多,不过对编译器内部对其实现知之甚少。所以借这次机会好好百度谷歌了一番。
static实现形式
我们都知道,static变量只能初始化一次,这个是怎么实现的呢?小齐的网易博客里面作者写的很清楚: 代码如下:
1 int main(){ 2 for (int i(10); i > 0; --i) 3 { 4 fun(i) ; 5 } 6 7 return 0; 8 } 9 10 void fun(int i) 11 { 12 static int n = i ; 13 int *p = &n ; 14 cout << n << " " ; 15 16 ++n ; 17 }
因为static变量只能被初始化一次,所以第12行的初始化语句只会被执行一次,但是以后每次都会执行++n的操作,所以输出的结果是:
10 11 12 13 14 15 16 17 18 19
之后作者在VS下面对上述程序进行了DEBUG调试,发现第一次n被赋值之前内存如下:
0042E058 00 00 00 00 ....
0042E05C 00 00 00 00 .... // 中间这个为n的内存地址
0042E060 00 00 00 00 ....
当初始化语句执行完毕,内存内容如下:
0042E058 01 00 00 00 ....
0042E05C 0A 00 00 00 ....// n
0042E060 00 00 00 00 ....
作者继续执行,当for循环执行的时候,再次对static n初始化的时候,却发现n值不会等于i,而是继续保留原来的值。由此作者推断上面的那个0x01是不是就是标志n已经被初始化的标志位。即当要对n初始化的时候,就会检查上面的0042E058单元中对应的标志位是否是1,若是1,则说明已经初始化,若不是1,则进行初始化。
鉴于此,作者改进了fun函数,如下:
void fun(int i) { static int n = i ; int *p = &n ; cout << n << endl ; ++n ; p--; *p=0; }
即每次修改0042E058内容清零,使得可以反复对n初始化,做了上述改动之后,函数执行的结果如下:
10 9 8 7 6 5 4 3 2 1
得到证实了,即即当要对static初始化的时候,就会检查上一单元中对应的标志位是否是1,若是1,则说明已经初始化,若不是1,则进行初始化。
之后作者又做了实验,他设定了两个static变量:
void fun(int i) { static int n1 = i ; static int n2 = i ; int *p = &n1 ; cout << n1 << endl ; ++n1 ; p--; *p=0; }
观察单元发现,当执行static赋值之前,内存单元内容如下:
0042E058 00 00 00 00 ....
0042E05C 00 00 00 00 .... // n1
0042E060 00 00 00 00 .... // n2
当执行完static int n1 = i 语句之后,内存的值变成这样了:
0042E058 01 00 00 00 ....
0042E05C 0A 00 00 00 ....
0042E060 00 00 00 00 ....
接着我们再单步static int n2 = i,则执行内存的值变成这样:
0042E058 03 00 00 00 ....
0042E05C 0A 00 00 00 ....
0042E060 0A 00 00 00 ....
这样就很明显了,编译器分别用一位来表示一个static变量是否已经始化。
const实现形式就比较简单了,作者想使用地址的形式改变const常量的值,可是没有成功,作者查看汇编代码,原来是编译器直接将常量类型的换成对应的数值了。这样在执行的时候直接使用数值替换。
至于const类型的变量,大家知道,在c语言中,下面的语句:
const int conVal=10; int *cPointer=&conVal;
是完全可以编译通过的,而且如果后面你通过*cPointer来改变conVal的值,也是大大的可以的,完全没有任何问题,编译器顶多弄一条警告而已。但是如果是在C++编译环境下面的话,再写上面两句话……结果%>_<%啊哦,那是一个错误“invalid conversion from 'const int*' to 'int*' [-fpermissive]|”。这时候如果我们还是想改变他们的值,那该怎么办呢?const_cast就出场了。
const int constant = 21; const int* const_p = &constant; int* modifier = const_cast<int*>(const_p); *modifier = 7;
编译运行成功O(∩_∩)O~。但是问题又来了,
cout << "constant: "<< constant <<endl;//21 cout << "const_p: "<< *const_p <<endl;//7 cout << "modifier: "<< *modifier <<endl;//7
cout << "constant: "<< &constant <<endl;//0x22fef4 cout << "const_p: "<< const_p <<endl;//0x22fef4 cout << "modifier: "<< modifier <<endl;//0x22fef4
等等,这谁能告诉我,到底发生了什么事情?同一个地址的东西,我换个方式读取,值就不一样?不过这也证实了一件事情,在C++确实不愧是多了两个+,constant变量必须是constant的,那就是不变,不论怎么cast,我就是不变。
IBM的C++指南:“*modifier = 7;”为“未定义行为(Undefined Behavior)”。所谓未定义,是说这个语句在标准C++中没有明确的规定,由编译器来决定如何处理。
好吧,这指南都说了,未定义行为,由编译器决定处理方式,那咱就不多说什么了吧。不过上面这种使用方式真的只能是纯属测试行为,我们在实际中用到const_cast一般是有两种情况:
定义了一个const变量,但是却需要把这个变量传递到一个没有const修饰的参数的函数中去:
void aTest(int* a){ //do something, this will not change the *a } int main(){ const int val=9; aTest(const_casr<int *>(&val)); return 0; }
定义了一个普通变量,中间是用了const指针,但是现在确又想把它变回来了……
int variable = 21; const int* const_p2 = &variable; int* modifier2 = const_cast<int*>(const_p2); *modifier2 = 7; cout << "variable:" << variable << endl;
其实对于const变量,是没有运行时检查的,只有在编译器编译的时候才会进行检查。而且在某些情况下,是会直接使用const变量进行替换的,如数组长度等情况。某些同学可能还会问const char* str="abdd"这个语句中,里面的字符串为什么有写保护呢?其实这个嘛,可以理解为这个字符串是放在常量区的,放在一个只读属性的页面上面,所以在改写的时候是会出错的。这个跟前面的const定义是没有关系的,因为就算没有const修饰,这个字符串也是不能修改的。