一个看似非常简单但却非常奇怪的问题

daheiantian 2011-06-01 03:18:04
引子:
对于下面的程序,我们很容易理解
int main()
{
int a =2;
int &b =a;
int &c =b;

cout<<"&a=="<<&a<<", &b=="<<&b<<", &c=="<<&c<<endl;
cout<<"a=="<<a<<", b=="<<b<<", &c=="<<c<<endl;
b=4;
cout<<"a=="<<a<<", b=="<<b<<", &c=="<<c<<endl;
return 0;
}
/*
运行结果如下
&a==0x22fee0, &b==0x22fee0, &c==0x22fee0
a==2, b==2, &c==2
&a==0x22fee0, &b==0x22fee0, &c==0x22fee0
a==4, b==4, &c==4
*/

这个大家可能都明白 因为a,b,c都是对应同一个地址,即变量a的地址,所以①三者值相同,并且②改变其中一个,其它两个也同时改变。

但是对于下面的代码,问题非常奇怪:
#include <vector>
#include <iostream>
using namespace std;
struct Point {
Point(int xx, int zz)
:a(xx),
ra(xx) {
cout<<"***in Point::Point** "<<endl<<"ra=="<<ra<<", a=="<<a <<endl;
cout<<"&ra=="<<&ra<<", "<<"&xx=="<<&xx<<endl;
}

//赋值操作符重载
Point& operator=(const Point &other) {
cout<<"hello"<<endl;
return *this;
}
int a;
int &ra;

};
int main() {
std::vector<Point> pts;
Point temp(2, 5);
Point temp2(temp);
pts.push_back( temp );
cout<<endl<<"***in main***"<<endl;
cout<<"pts[0].ra == "<<pts[0].ra<<", "<<"& pts[0].ra=="<<&(pts[0].ra)<<endl;
cout<<"temp.ra=="<<temp.ra<<", "<<"& temp.ra=="<<&(temp.ra)<<endl;
cout<<"temp2.ra=="<<temp2.ra<<", "<<"& temp2.ra=="<<&(temp2.ra)<<endl;

temp.ra = 88 ; //改变temp.ra的值

cout<<endl;
cout<<"pts[0].ra == "<<pts[0].ra<<", "<<"& pts[0].ra=="<<&(pts[0].ra)<<endl;
cout<<"temp.ra=="<<temp.ra<<", "<<"& temp.ra=="<<&(temp.ra)<<endl;
cout<<"temp2.ra=="<<temp2.ra<<", "<<"& temp2.ra=="<<&(temp2.ra)<<endl;

return 0;
}
/*
运行结果:
***in Point::Point**
ra==2, a==2
&ra==0x22fe64, &xx==0x22fe64

***in main***
pts[0].ra == 0, & pts[0].ra==0x22fe64
temp.ra==4621588, & temp.ra==0x22fe64
temp2.ra==4621588, & temp2.ra==0x22fe64

pts[0].ra == 0, & pts[0].ra==0x22fe64
temp.ra==4621588, & temp.ra==0x22fe64
temp2.ra==4621588, & temp2.ra==0x22fe64

*/

说明:
由于没有在Point类中提供复制构造函数,所以编译器会默认提供“复制构造函数”,其行为是将类的引用成员ra绑定到了变量xx上,(这里xx是局部变量)
我知道绑定到局部变量之后,随着函数的结束,变量xx会被撤销,可能造成内存指向错误,但毕竟是指向相同的内存地址。

问题:
1. pts[0].ra temp.ra temp2.ra三者也是相同地址,虽然所指向的地址已经撤销了,但是还是指向同一个地址了的。
那么三者打印出来的值为什么不一样呢????(pts[0].ra的值为0??)
2. 程序中使用“temp.ra = 88”,修改了temp.ra的值,但是为什么打印出来的结果是“没有修改”的呢???
3. 还有一个小疑惑,对于vector,我们知道是先开辟内存,然后push_back时,利用“复制构造函数”进行初始化,不是赋值操作符哦,
这里不提供赋值操作符重载(而编译器会自动合成复制构造函数,尽管可能不正确,这里是将引用进行简单的绑定),为什么编译通不过呢?是我记错了,还是另有原因??
...全文
2154 31 打赏 收藏 转发到动态 举报
写回复
用AI写文章
31 条回复
切换为时间正序
请发表友善的回复…
发表回复
daheiantian 2011-06-18
  • 打赏
  • 举报
回复
明天得做个总结,差点忘记结贴……
blackneck 2011-06-07
  • 打赏
  • 举报
回复
学习下
ri_aje 2011-06-06
  • 打赏
  • 举报
回复
25 楼,Point 构造函数写成这个模样,

Point(int xx, int zz)
:a(xx),
ra(xx)

ra 引用的是构造函数的参数 xx,后者构造函数结束后,暂用的内存就不存在了,ra 就引用的一个已经不存在的东西。 temp2 是 temp 的副本,其 ra 也是引用的不存在的东西,pts[0] 在最好的情况下是 temp 的副本,其 ra 也是引用的不存在的东西。
Evanders 2011-06-05
  • 打赏
  • 举报
回复
我估计这个是楼主编译器的问题。。我这VS2010结果一样。

***in Point::Point**
ra==2, a==2
&ra==0012FE3C, &xx==0012FE3C

***in main***
pts[0].ra == 1244732, & pts[0].ra==0012FE3C
temp.ra==1244732, & temp.ra==0012FE3C
temp2.ra==1244732, & temp2.ra==0012FE3C

pts[0].ra == 1244732, & pts[0].ra==0012FE3C
temp.ra==1244732, & temp.ra==0012FE3C
temp2.ra==1244732, & temp2.ra==0012FE3C
请按任意键继续. . .

Louistao 2011-06-05
  • 打赏
  • 举报
回复
。。。再顶下
Louistao 2011-06-05
  • 打赏
  • 举报
回复
为什么说temp.ra的内存不存在了?
Louistao 2011-06-05
  • 打赏
  • 举报
回复
我的是G++ 打印出来也是楼主这种情况。楼上所说的意思是pts[0].ra是无效内存。这个我有点不理解?哪个能再详细说说吗
ri_aje 2011-06-05
  • 打赏
  • 举报
回复
CSDN 吃了我好多空格啊,影响阅读,强行帖成代码试试。


23.1.2/table 68
a.push_back(x) void a.insert(a.end(),x) vector, list, deque

23.1.1/table 67
a.insert(p,t) iterator inserts a copy of t before p.

21.1/table 64
expression return type post-condition
t = u T& t is equivalent to u

ri_aje 2011-06-05
  • 打赏
  • 举报
回复
1. pts[0].ra temp.ra temp2.ra三者也是相同地址,虽然所指向的地址已经撤销了,但是还是指向同一个地址了的。那么三者打印出来的值为什么不一样呢????(pts[0].ra的值为0??)

我用若干个 g++ 版本和 VS2010 试过,地址和数值都是一样的。另外,*.ra 已经是无效引用的,引用的内存根本不受编译器保护,很可能被无端占用,所以看他们是否一样,涉及的操作越少越好,可以像下面这样试一下。

if (temp.ra == temp2.ra && temp.ra == pts[0].ra)
{
cout << "all same" << endl;
}

要是看到输出,就知道一样了,release 模式下 pts[0] 多半会优化没了的,这样 *.ra 涉及的无效内存可能就不会被莫名其妙的动了。

2. 程序中使用“temp.ra = 88”,修改了temp.ra的值,但是为什么打印出来的结果是“没有修改”的呢???

内存都不存在了,这个是标准的未定义行为,什么值都是正常的。

3. 还有一个小疑惑,对于vector,我们知道是先开辟内存,然后push_back时,利用“复制构造函数”进行初始化,不是赋值操作符哦,这里不提供赋值操作符重载(而编译器会自动合成复制构造函数,尽管可能不正确,这里是将引用进行简单的绑定),为什么编译通不过呢?是我记错了,还是另有原因??

楼主恐怕是记错了,标准从来没说 push_back 要利用复制构造函数进行初始化。标准对于 std::vector::push_back 具体行为的规范散落在各处,现归纳如下:
23.1.2/table 68
a.push_back(x) void a.insert(a.end(),x) vector, list, deque
说明标准要求 push_back 和 insert(a.end,x) 具有相同的效果。

23.1.1/table 67
a.insert(p,t) iterator inserts a copy of t before p.
这里说 insert(p,t),也就是 push_back 的效果是在迭代器 p 之前插入 t 的副本。对与push_back, p 就是a.end()。注意标准的措辞是 a copy of t,这中说法只规定了 insert 的效果,并没有规定达到效果的手段,通过“复制构造函数”构造能够达到这样的效果,通过“赋值”也能够达到同样的效果。鉴于楼主说不提供operator= 就会产生编译错误,可见该编译器是通过“赋值”实现的,而这也符合标准。这也解释了为什么不提供operator=就会出错,因为具体vector::push_back的实现用到了operator=,而编译器无法生成默认赋值操作符,后者是因为类中包含引用非静态成员变量造成的,具体规范参见标准,12.8/12:
An implicitly-declared copy assignment operator is implicitly defined when an object of its class type is assigned a value of its class type or a value of a class type derived from its class type. A program is ill-formed if the class for which a copy assignment operator is implicitly defined has:
— a nonstatic data member of const type, or
— a nonstatic data member of reference type, or

注意此条并没有相应的出现在编译器默认生成的复制构造函数的规范中,否则以下这种用法都是错误的

Point temp2(temp);


另外,楼主这样定义的时候,

std::vector<Point> pts;

已经默默违反标准对于容器存储内容的要求了。参见

21.1/table 64 assignable requirements
expression return type post-condition
t = u T& t is equivalent to u

而楼主的 Point 类显然无法满足这一点,往标准容器中放不满足要求的类型基本就是自找麻烦。
go_Michael 2011-06-03
  • 打赏
  • 举报
回复
第三个问题不清楚,坐等高人解答
daheiantian 2011-06-01
  • 打赏
  • 举报
回复
[Quote=引用楼主 daheiantian 的回复:]
3. 还有一个小疑惑,对于vector,我们知道是先开辟内存,然后push_back时,利用“复制构造函数”进行初始化,不是赋值操作符哦,
这里不提供赋值操作符重载(而编译器会自动合成复制构造函数,尽管可能不正确,这里是将引用进行简单的绑定),为什么编译通不过呢?是我记错了,还是另有原因??
C[/Quote]

最后一个问题的原因是什么?
daheiantian 2011-06-01
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 luciferisnotsatan 的回复:]
***in Point::Point**
ra==2, a==2
&ra==0012FF28, &xx==0012FF28

***in main***
pts[0].ra == 4207060, & pts[0].ra==0012FF28
temp.ra==4207100, & temp.ra==0012FF28
temp2.ra==4207124,……
[/Quote]
我用g++下调试了一下,就是因为出栈入栈的问题。虽然不太懂这里的01级调试是什么,但是算是找到问题的原因。每一个时刻,三个变量的值都是相同的,即使是pts[0].ra为0的时刻,其他两个变量也都是0。至于为什么只有pts[0].ra为0,应该是因为形式参数中调用了vector中的重载操作符[],也就是产生了两个函数调用,调用[]导致覆盖后值变成了0。
另外我发现,如果将原来的句子分开单独执行,在执行cout<<temp.ra;在这一时刻,temp.ra值不变化,说明cout<<temp.ra; 调用cout的<<重载函数不会覆盖temp.ra所在的内存。

luciferisnotsatan 2011-06-01
  • 打赏
  • 举报
回复
***in Point::Point**
ra==2, a==2
&ra==0012FF28, &xx==0012FF28

***in main***
pts[0].ra == 4207060, & pts[0].ra==0012FF28
temp.ra==4207100, & temp.ra==0012FF28
temp2.ra==4207124, & temp2.ra==0012FF28

pts[0].ra == 4207060, & pts[0].ra==0012FF28
temp.ra==4207100, & temp.ra==0012FF28
temp2.ra==4207124, & temp2.ra==0012FF28

vc2005, 开启 01 级优化时,数字不一样。
你用cout输出,那是输出不同时刻,那个地址上的值。同一时刻,值只会是一个(你可以用调试器看)。不同时刻,值可能会不同,也可能相同。
luciferisnotsatan 2011-06-01
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 daheiantian 的回复:]

引用 11 楼 luciferisnotsatan 的回复:
引用 10 楼 tompaz 的回复:

引用 3 楼 dizuo 的回复:

temp.ra = a ; //改变temp.ra的值
========
再次给ra赋值,改变的只是ra初始化时候引用的变量,然后这个变量早已析构。。。


相对应的栈内存应该也会改变才对啊

调试单步跟踪下
temp.ra = ……
[/Quote]
这个不太清楚,可能gcc编出的汇编码不同导致的。你的代码里也没有0。
最好你还是用调试器,看看那一时刻的值是什么。

cout<<"pts[0].ra == "<<pts[0].ra<<", "<<"& pts[0].ra=="<<&(pts[0].ra)<<endl;
这里输出 pts[0].ra的值后,还有很多出入栈操作。
cout<<"temp.ra=="<<temp.ra<<", "<<"& temp.ra=="<<&(temp.ra)<<endl;
输出temp.pa也是一个函数调用,同样也出入栈
daheiantian 2011-06-01
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 luciferisnotsatan 的回复:]
引用 10 楼 tompaz 的回复:

引用 3 楼 dizuo 的回复:

temp.ra = a ; //改变temp.ra的值
========
再次给ra赋值,改变的只是ra初始化时候引用的变量,然后这个变量早已析构。。。


相对应的栈内存应该也会改变才对啊

调试单步跟踪下
temp.ra = 88 ; 这行运行后,对应的是变成88了
但继续运行下面的代……
[/Quote]
好像明白了,调用cout的<<的时候会重新覆盖栈内存,但是为什么pts[0].ra会是0呢,既然是同一个地址,那么无论gcc还是vc都应该一样吧
luciferisnotsatan 2011-06-01
  • 打赏
  • 举报
回复
[Quote=引用 15 楼 daheiantian 的回复:]

引用 7 楼 zhanghengsdnu 的回复:
2.形参必须是引用类型。

C/C++ code
struct Point {
Point(int &amp;xx, int zz)
:a(xx),
ra(xx) {
cout<<"***in Point::Point** "<<endl<<"ra=="<<ra<<", a=="<<a <<endl;
…………
[/Quote]
不使用引用,int xx,这个是形参,一个局部变量,函数退出,就回收了。这样ra引用的那个地址也就回收了,如同野指针。
daheiantian 2011-06-01
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 zhanghengsdnu 的回复:]
2.形参必须是引用类型。

C/C++ code
struct Point {
Point(int &xx, int zz)
:a(xx),
ra(xx) {
cout<<"***in Point::Point** "<<endl<<"ra=="<<ra<<", a=="<<a <<endl;
……
[/Quote]
是的,正常写程序是这样的,但是这里想探讨不使用引用会出现什么情况?
toadzw 2011-06-01
  • 打赏
  • 举报
回复
野指针。。。
栈变量。。。

这两个引起的
KID_coder 2011-06-01
  • 打赏
  • 举报
回复
野指针。。。
栈变量。。。

这两个引起的
T_Sky 2011-06-01
  • 打赏
  • 举报
回复
[Quote=引用 9 楼 zhanghengsdnu 的回复:]

3.给你个总结吧:
1.引用类型的成员变量不能直接在构造函数里初始化,必须用到初始化列表,且形参也必须是引用类型。
2.凡是有引用类型的成员变量的类,不能有缺省构造函数。
[/Quote]
总结的好。
加载更多回复(11)

64,654

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧