内存中的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修饰,这个字符串也是不能修改的。

posted on 2014-02-21 18:17  晓O(∩_∩)O~  阅读(503)  评论(0编辑  收藏  举报