指针作用域

本文探讨了C++中指针与作用域的相关问题,包括野指针的产生原因及如何避免,以及不同类型变量(如对象、整型变量、字符串)在不同作用域下的行为表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<script src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
关闭
width="300" height="250" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" onload="var i=this.id,s=window.google_iframe_oncopy,H=s&&s.handlers,h=H&&H[i],w=this.contentWindow,d;try{d=w.document}catch(e){}if(h&&d&&(!d.body||!d.body.firstChild)){if(h.call){setTimeout(h,0)}else if(h.match){try{h=s.upd(h,i)}catch(e){}w.location.replace(h)}}" id="aswift_0" name="aswift_0" style="left:0;position:absolute;top:0;">
href="http://static.blog.youkuaiyun.com/css/comment1.css" type="text/css" rel="stylesheet" /> href="http://static.blog.youkuaiyun.com/css/style1.css" type="text/css" rel="stylesheet" /> rel="stylesheet" href="http://static.blog.youkuaiyun.com/public/res-min/markdown_views.css?v=1.0" /> rel="stylesheet" href="http://static.blog.youkuaiyun.com/css/category.css?v=1.0" />

C++ 指针与作用域

标签: 指针作用域
1137人阅读 评论(0) 收藏 举报
分类:

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


源码1:

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

class A
{
public:
    void Func(void)
    {
        cout<<"Func of class A"<<endl;
    }
};

void Test(void)
{
    A *p;
    {
        A a;
        p = &a; // 注意 a 的生命期
    }
    p->Func(); // p是“野指针”
}

int main()
{
    Test();
    return 0;
}

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

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

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

源码2:

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

class A
{
    int i;
    char chr;
};
class B
{
    int i;
    char chr;
    void fun()
    {
        int j = 1;
        cout<<j<<endl;
    }
};
int main()
{
    A a;
    B b;
    cout<<"sizeof A = "<<sizeof(a)<<endl;
    cout<<"sizeof B = "<<sizeof(b)<<endl;
    return 0;
}

输出为:

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

char *getstr(int flag)
{
    if(flag == 1)
    {
        char *str = "12345";//字符串"12345"存放在文字常量区
        return str;
    }
    else
    {
        char str[] = "ABCDE";//字符数组
        return str;
    }
}

int *getint()
{
    int i = 2014;
    return &i;
}

int main()
{
    //字符串类型测试
    cout<<"字符串类型测试(字符串):"<<getstr(1)<<endl;
    cout<<"字符串类型测试(字符数组):"<<getstr(2)<<endl;
	//char *str = getstr(2);//使用指针str保存返回的getstr(2)指针,打印出来的是乱码,说明指针没有正确返回
	//使用getstr(2)打印确实正常的,如下:
	for(int i = 0; i < 6; i++)
	{
		cout<<"str"<<i<<": "<<getstr(2)[i]<<endl;
	}
	

    cout<<endl<<"****************分割线******************"<<endl<<endl;
    //整型类型测试
    cout<<"整型类型测试:"<<*getint()<<endl;
    return 0;
}


运行结果(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++中的内存管理学问太深,这篇博客是小弟个人拙见,也许漏洞百出,还是希望各路高手指教,不胜感激!


本文版权归作者和优快云所有,欢迎转载,转载请注明出处:http://blog.youkuaiyun.com/thebestdavid/article/details/23165597

0
0
 
 
我的同类文章
width="728" height="90" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" onload="var i=this.id,s=window.google_iframe_oncopy,H=s&&s.handlers,h=H&&H[i],w=this.contentWindow,d;try{d=w.document}catch(e){}if(h&&d&&(!d.body||!d.body.firstChild)){if(h.call){setTimeout(h,0)}else if(h.match){try{h=s.upd(h,i)}catch(e){}w.location.replace(h)}}" id="aswift_1" name="aswift_1" style="left:0;position:absolute;top:0;">
rel="stylesheet" href="http://static.blog.youkuaiyun.com/css/replace.css" />

参考知识库

img
.NET知识库

img
C++知识库

img
软件测试知识库

猜你在找
width="728" height="90" frameborder="0" marginwidth="0" marginheight="0" vspace="0" hspace="0" allowtransparency="true" scrolling="no" allowfullscreen="true" onload="var i=this.id,s=window.google_iframe_oncopy,H=s&&s.handlers,h=H&&H[i],w=this.contentWindow,d;try{d=w.document}catch(e){}if(h&&d&&(!d.body||!d.body.firstChild)){if(h.call){setTimeout(h,0)}else if(h.match){try{h=s.upd(h,i)}catch(e){}w.location.replace(h)}}" id="aswift_2" name="aswift_2" style="left:0;position:absolute;top:0;">
查看评论

  暂无评论

发表评论
  • 用 户 名:
  • WY1468840047
  •   
* 以上用户言论只代表其个人观点,不代表优快云网站的观点或立场
  • 个人资料
    • 访问:89461次
    • 积分:1122
    • 等级:
      积分:1122
    • 排名:千里之外
    • 原创:23篇
    • 转载:0篇
    • 译文:0篇
    • 评论:246条
  • 文章分类
  • 最新评论
您有 118条新通知
收藏助手

提问

您的问题将会被发布在“技术问答”频道 ×
该问题已存在,请勿重复提问
插入图片
| | | | | |
  
 
 
0 0 0:0
推荐标签:
我要悬赏
取消 发布
可能存在类似的问题:

保存代码片

整理和分享保存的代码片,请访问代码笔记
  • *标题
  • *描述
  •  标签
    指针x 作用域x
0个广告已降服
可以清爽的上网了~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值