当前位置:   article > 正文

C++ 指针与作用域_c++ 指针定义的作用域

c++ 指针定义的作用域

C/C++中的指针操作是一个令人抓狂的问题,这几天在温习林锐的《高质量C++C编程指南》,里面的内存管理这一章对我受益匪浅。看到下面的一段内容,不禁和作者提出相同的疑问:该程序不出错是因为编译器的原因吗?并在网上查找相关资料。


源码1:

  1. #include <iostream>
  2. using namespace std;
  3. class A
  4. {
  5. public:
  6. void Func(void)
  7. {
  8. cout<<"Func of class A"<<endl;
  9. }
  10. };
  11. void Test(void)
  12. {
  13. A *p;
  14. {
  15. A a;
  16. p = &a; // 注意 a 的生命期
  17. }
  18. p->Func(); // p是“野指针”
  19. }
  20. int main()
  21. {
  22. Test();
  23. return 0;
  24. }

为了探索这个问题,我分别在CodeBlocks、VC++6.0和VS2010上运行这段代码,发现并无错误,所以,这个并非编译器的原因,那么到底是什么原因呢?

1、我们知道在Test函数中内部用花括号括起来的部分是一个局部作用范围,因此,该语句块拥有局部的作用域,在里面定义的局部对象a,是一个存放在栈中的自动变量,一旦离开了这个作用域(离开花括号),就不复存在了。可以通过简单的单步调试,跟踪对象a的变化,实践证明,这个想法是对的。

2、一般来说,一个对象所占的空间大小只取决于该对象中数据成员所占的空间,而与成员函数无关。通过下面一段代码可以验证

源码2:

  1. #include <iostream>
  2. using namespace std;
  3. class A
  4. {
  5. int i;
  6. char chr;
  7. };
  8. class B
  9. {
  10. int i;
  11. char chr;
  12. void fun()
  13. {
  14. int j = 1;
  15. cout<<j<<endl;
  16. }
  17. };
  18. int main()
  19. {
  20. A a;
  21. B b;
  22. cout<<"sizeof A = "<<sizeof(a)<<endl;
  23. cout<<"sizeof B = "<<sizeof(b)<<endl;
  24. return 0;
  25. }

输出为:

,在类A和类B中都有一个int和char类型的成员变量,根据内存对齐,取其中较大的int变量的字节数4作为倍数,所以sizeof(A) = 2 * 4 = 8字节。尽管类B中有成员函数fun,但是该对象b所占用空间并没有计算fun函数的空间。

3、那么成员函数放在哪里?对象是怎么调用成员函数的呢?

先来看一个图片:



通过图片可以看出,对象实例的成员函数(非静态成员函数)有一个公用函数代码存储空间,对象通过this指针调用公用函数代码中的成员函数,这样可以减少存储空间的使用。

小结:1) 不论成员函数在类内定义还是在类外定义,成员函数的代码段都用同一种方式存储;

    2) 函数代码是存储在对象空间之外的;

    3) 通常所说的“某某对象的成员函数”,是从逻辑的角度而言的;而成员函数的存储方式,是从物理的角度而言的,二者是不矛盾的。

4、返回原来讨论的问题,既然成员函数存放在公用函数代码段里面,而在源码1中,指针p 是保存了在花括号里面的对象a的地址的,对象a销毁了,我们不能使用变量a,但是它的地址是不会改变的,编译器只是销毁了对象a这个变量,并没有销毁对象a原来所在存储区域(当然是在栈里面的存储区域)中的成员变量和成员函数,所以,这里得到的指针p,还是指向花括号中定义的对象a的首地址,这就是ptr就是原来的对象a的this指针,当使用p->Func();语句调用成员函数的时候,自然就可以通过this指针在公用函数代码段中找到对应的成员函数,执行该函数(我猜想在公用函数代码段中应该维护了一个this指针与成员函数的对照表)。

5、错误的调用返回了正确的结果,着实令人头痛。显然,应该坚决杜绝这样的调用方法。对于野指针(不是NULL指针,是指向被释放的或者访问受限内存的指针),我们使用delete或者free释放存储空间的时候,把指针p赋值为NULL是一个好习惯,这样,通过if(p != NULL)语句就可以判断该指针是否是合法的。


扩展:

上述研究的内容是针对一个对象实例的,假设是一个int类型或者字符串类型,结果是否一样呢。来看一段代码。

源码3:

  1. #include <iostream>
  2. using namespace std;
  3. char *getstr(int flag)
  4. {
  5. if(flag == 1)
  6. {
  7. char *str = "12345";//字符串"12345"存放在文字常量区
  8. return str;
  9. }
  10. else
  11. {
  12. char str[] = "ABCDE";//字符数组
  13. return str;
  14. }
  15. }
  16. int *getint()
  17. {
  18. int i = 2014;
  19. return &i;
  20. }
  21. int main()
  22. {
  23. //字符串类型测试
  24. cout<<"字符串类型测试(字符串):"<<getstr(1)<<endl;
  25. cout<<"字符串类型测试(字符数组):"<<getstr(2)<<endl;
  26. //char *str = getstr(2);//使用指针str保存返回的getstr(2)指针,打印出来的是乱码,说明指针没有正确返回
  27. //使用getstr(2)打印确实正常的,如下:
  28. for(int i = 0; i < 6; i++)
  29. {
  30. cout<<"str"<<i<<": "<<getstr(2)[i]<<endl;
  31. }
  32. cout<<endl<<"****************分割线******************"<<endl<<endl;
  33. //整型类型测试
  34. cout<<"整型类型测试:"<<*getint()<<endl;
  35. return 0;
  36. }


运行结果(VC++6.0):


1) 对于整型变量来说,与之前的说法是一致的,getint方法中变量i在退出函数时便不能使用了,但是返回的地址所指向的存储区域内容并没有改变依旧是2014;

2) char *str = "12345";这种定义方式的字符串"12345"是存放在文字常量区的,该字符串不能修改,其生命周期和整个程序的生命周期一样长,但是依旧不建议这么编写代码。

3) char str[] = "ABCDE";这里定义的是一个字符数组,这是一种直观的初始化方式,注意这样不代表字符串"ABCDE"是存放在文字常量区,而是等价于char str[] = {'A', 'B', 'C', 'D', 'E', '\0'};;离开了getstr函数,字符数组str便不复存在,这里返回的指针不一定是原先字符串"ABCDE"的首地址,这里打印出来的地址是乱码,但是单个取值却没有问题。个人猜想:数组名和指针不是一个概念,getstr函数中分配字符数组空间的时候,还要存储数据的类型等信息,返回的也是数组名str,然后离开了getstr函数,数组str不存在了,返回的“数组名”是没有意义的,因此打印出来的是乱码。


总结:

离开了作用域的变量,变量被销毁,变量所在存储区域的内容并没有被系统自动实时释放和回收;也许该存储区域只是做了“可分配”标记。


注:不能因为系统没有实时回收这些变量而存在侥幸心理,熟练使用指针并对内存管理有清晰的理解才是王道。


C/C++中的内存管理学问太深,这篇博客是小弟个人拙见,也许漏洞百出,还是希望各路高手指教,不胜感激!


本文版权归作者和CSDN所有,欢迎转载,转载请注明出处:http://blog.csdn.net/thebestdavid/article/details/23165597

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/63273
推荐阅读
相关标签
  

闽ICP备14008679号