版权声明:本文为博主原创文章,未经博主允许不得转载。
C/C++中的指针操作是一个令人抓狂的问题,这几天在温习林锐的《高质量C++C编程指南》,里面的内存管理这一章对我受益匪浅。看到下面的一段内容,不禁和作者提出相同的疑问:该程序不出错是因为编译器的原因吗?并在网上查找相关资料。
源码1:
- #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;
- }
#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:
- #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;
- }
#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:
- #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;
- }
#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++中的内存管理学问太深,这篇博客是小弟个人拙见,也许漏洞百出,还是希望各路高手指教,不胜感激!
本文版权归作者和CSDN所有,欢迎转载,转载请注明出处:http://blog.csdn.net/thebestdavid/article/details/23165597
-
顶
- 0
-
踩
- 0
我的同类文章
- •Windows下配置ftp服务器,使用QT实现文件上传和下载2014-07-16
- •人人校招笔试题2013-09-29
- •阿里巴巴笔试题选解2013-09-24
- •动态规划---LIS2013-10-19
- •二维数组---两个数和的最大值2013-09-21
- •百度2014校招笔试题目题解(更新了第1题的算法,10.9下午)2013-10-02
- •腾讯2014软件开发笔试题目2013-09-26
- •创新工场笔试题2013-09-23
- •动态规划--凑硬币问题2013-10-11
- •平面直角坐标系---点坐标与多边形位置判断2013-09-17
核心技术类目
- 个人资料
-
- 访问:89461次
- 积分:1122
- 等级:
- 排名:千里之外
- 原创:23篇
- 转载:0篇
- 译文:0篇
- 评论:246条
- 文章搜索
- 阅读排行
- 阿里巴巴笔试题选解(37883)
- 腾讯2014软件开发笔试题目(17229)
- 百度2014校招笔试题目题解(更新了第1题的算法,10.9下午)(7167)
- 创新工场笔试题(3352)
- Windows下配置ftp服务器,使用QT实现文件上传和下载(2216)
- 人人校招笔试题(2145)
- 面试题目“ABCDE × 4 = EDCBA”新解法(1695)
- 动态规划--子序列的个数(1411)
- 批处理文件——多个QQ一键登录(1373)
- POJ1002 487-3279(1294)
- 评论排行
- 阿里巴巴笔试题选解(123)
- 腾讯2014软件开发笔试题目(43)
- 百度2014校招笔试题目题解(更新了第1题的算法,10.9下午)(38)
- 创新工场笔试题(11)
- 倒水(7)
- 人人校招笔试题(5)
- 建立信号基站(4)
- 动态规划--子序列的个数(4)
- 面试题目“ABCDE × 4 = EDCBA”新解法(4)
- 字符串消除(4)
- 最新评论
- 阿里巴巴笔试题选解
lotluck:我感觉第一个题应该是15啊,因为3个节点可能不一样
- 阿里巴巴笔试题选解
LuBin5488:作者的第三题是不是可以在做完第一次N/2比较后,然后分开的两个数组分别用败者树和胜者树 这样总共的时...
- 阿里巴巴笔试题选解
liuhmmjj:@yangjingxu:思路很好
- 创新工场笔试题
zwjcxj:楼主给力!~多谢多谢
- C语言的几个有趣问题
JAVA鱼翔浅底:可以嘛 我乍一看都蒙了
- 腾讯2014软件开发笔试题目
hhaahddda:感谢解答,第二种方法用的很巧妙,但是第一种你的代码对吗?快排函数里面的参数都代表什么啊?能详细说说吗
- 人人校招笔试题
大卫david:@xin_jmail:仔细看题目,是“超过半数”的一个数,请问你给的例子哪个数字超过半数?
- 人人校招笔试题
HamaWhite:第二题得判断VeryNum是否>n/2,要不然这个{1,3,2,2,2,3,4,3}就判断不出来了吧...
- 阿里巴巴笔试题选解
HamaWhite:@xuzewei_2:不过还是很佩服作者的数学推导能力!
- 腾讯2014软件开发笔试题目
baidu_16186957:动态规划,如需输出的话,还需枚举指路(因为它不是连续的串,很可能是散开的)。
暂无评论