注册 登录
编程论坛 C++教室

[原创]C++中的指针(一) 简单指针

myajax95 发布于 2006-07-31 01:32, 5454 次点击

简单总结一下C++中指针的用法,以后再写一篇详细的,关于smart pointer的总结。

指针的定义很简单。在变量前打个星。例如一个class的名字叫A,那么指针定义为
A *pa;

有意点点另人混淆的是指针和const的混用。
char chr[] = "abc";
const char *p = chr; //这里p不是常数指针,而是把指针指向的地址定义为了常数。无论chr本身是不是指向常数内存区,但只要用p去操作,那么就不可以通过p去修改其内容。

chr[2] = 'e'; // ok
p[2] = 'd'; // error

p+=1; // ok, 改的是p指向的地址而不是p的内容。

真正的常数指针这么写
char *const cp = s;
这时在常数内存中allocate了一个指针的控件存储cp,cp,也就是这个地址不能改,而其指向的内存的值可以修改。
chr[2] = 'w'; //ok
cp[2] = 'y'; // ok
cp+=1; // error

char* 可以被转换成const char*,因为操作后没有负面影响。反过来const char* 不能转换成char*,如果可以的话会把本部可写的内存的数据改掉。
// good example
char chr[] = "abc";
char *p = chr;
const char *cp = p;

// bad example
char chr[] = "abc";
const char *p = chr;
char *p = cp; // error.

这种转换常用在函数调用上,例如strcpy(char* source, char*dest)。这个操作只是想修改source,dest只是用于参考。为了避免函数修改dest可以把函数定义成strcpy(char* source, const char* dest)。

基本定义就这些了。对于指针的cast,C++作得比C更安全。例如有两个完全不相干的class A和B。
B b;
A *p1,*p2;

p1 = (A *)(&b); // 这是C式的cast,不管A和B有什么关系,强型转换。后果不堪设想。
C++中引入了static_cast操作,在一定程度上保护了操作的安全性,static_cast检查操作数与要操作的类型是否匹配,匹配是有class继承关系,无论谁继承谁都可以。如果这个关系不存在,出编译错误。
p2 = static_cast<A *>(&b); //error。
但是这个检查是不完全的,
class C : public A {}
C* pc = static_cast<A *>(p1); // ok. 因为pc,p2欧继承关系。

C++引入了RTTI得概念(Run Time Type Info)。通过dynamic_cast操作,可以检查操作数的内容,以确认这个操作是否成功。检查内容的方法就是把相关类型的继承关系和vtable都查一下。
p2 = dynamic_cast<A *>(p1);
在VC下使用dynamic_cast别忘了在当前Project-->Setting下选Enable Run Time Type Info。如果忘了选这个,debug模式下编译会不通过,release模式下会编译通过,运行时Crash。

dynamic_cast比较复杂,另外Visual C++各不同版本的表现不一样,这里详细说一下我学到的和试出来的。一般书上说是三种不同情况,考虑到Visual C++版本的问题,我分五个情况讨论。
1。upcast。从派生类向基类的转换,只要基类的继承关系是唯一的,就会成功,如果不唯一会有warning:"dynamic_cast used to convert to inaccessible or ambiguous base;"
下下面的例子中
class A{public: virtual void a(){}};
class B : public A {};
class C : public B {};
class D : public B {};
class E : public C, public D {};

int main()
{
E e, *pe = &e;
C *pc = dynamic_cast<C*>(pe);
B *pb = dynamic_cast<B*>(pe);
return 0;
}
转换pb一行会有warning,而且得到NULL指针。
其继承关系如下
A
/ \
B B
| |
C D
\ /
E
E到C成功,E到B失败因为不知道怎么转换。同样E到A也会失败。注意这里的检查只是指针类型pe的检查,没有查pe指向的object。把pe改成*pe = (E*)(new D());的话pe到pc的cast还会成功,不过pe到pb的cast会出现crash。这和dynamic_cast的实现有关,这个exception不是bad_cast,所以最好用try{} chatch(...)接着以防不测。

2。对于同类指针的cast应该是直接通过, 不对指针所指的object进行run time check。
int main()
{
A *p1 = (A*)0x1;
A *p2 = dynamic_cast<A*>(p1);
return 0;
}
但是VC6中竟对p1所指的地址进行了检查,这是VC6对ISO C++ standard实现不对的地方,在2003/2005中得到了修正。

3。downcast
从基类向派生类转换,指针指向的object会被检查,还以刚才的结构
E e;
A *pa = dynamic_cast<A*>((D*)&e);
C *pc = dynamic_cast<C*>(pa);
这个pc的cast会成功。

4。crosscast
class A{public: virtual void a(){}};
class B : public A {};
class C : public A {};
class D{public: virtual void d(){}};
class E : public B, public C, public D {};

int main()
{
E e;
C *pc = dynamic_cast<C*>((D*)&e);
return 0;
}
这个继承关系如下
A
/ | \
B C D
\ | /
E
从D到C的cast叫cross cast,这时查指针指的object的内容。这个具体例子中pc的cast 成功,因为确实有继承关系。
一个不理解的问题是下面的测试:
C c, *pc = dynamic_cast<C*>((D*)&c);
无论什么道理pc都应该成功,结果在VC6,VC2003中都成功了,在VC2005竟然失败,得到NULL。实在不明白,在MSDN的"Breaking Changes in dynamic_cast"也没有明确表述。只有死记住了。

总之,dynamic_cast如果成功,p2得到一个合法地址,也就是p1指向的地址。如果失败就不好说了,书上说会得到NULL,这是理想情况,p1,p2有相近的vtable。如果p1,p2的vtable完全不相干,或者一个class B根本没有vtable,dynamic_cast就会出exception,这不是bad_cast的exception,而是C++ first class exception。所以写别人程序传来的指针的时候别指望dynamic_cast管理一切,老老实实catch所有exception。
p2 = NULL;
try
{
p2 = dynamic_cast<A *>(p1);
}
catch (...) {}

if (!p2)
cout << "Bad cast".


另外两种cast不太常用reinterpret_cast提供很少的保护,几乎和C的cast差不多。const_cast得到最开始的变量的指针,可以用来改变常量的设置。这不是个好习惯,能不用最好不用。

smart pointer C++中一个很有用的概念,它对内存的管理起到了很大的帮助。由于内容比较多,回头我会写一篇详细的总结。

[此贴子已经被作者于2006-8-19 7:20:23编辑过]

32 回复
#2
michaelsoft2006-07-31 09:12
如果B没有vtable,编译应该会通不过的,说它不是多态类。另外你说的vtable的相似或相干是什么意思?是指虚函数表的结构吗?dynamic_cast只是借助type_info结构执行转换,对于多态类,type_info的指针通常放在vtable第一个条目之前,但转换应该跟虚函数表的结构没有必然的联系。可不可以给一段dymanic_cast引发非bad_cast异常的代码?
#3
myajax952006-07-31 09:52
如果B没有vtable,编译可以通过,但是dynamic_cast会throw exception,因为编译的时候无法知道p1和B的直接关系,而运行中dynamic_cast去查type_info的时候假设vtable存在,结果出现access violation。
#4
michaelsoft2006-07-31 11:17
你用的是什么编译器?我的是vs2003,如果B中不含虚函数,会导致C2683错误:“B”不是多态类型。不知道标准C++对这方面有没有要求。
#5
michaelsoft2006-07-31 11:50
我忽然觉得咱们讨论的好像不是一个问题,我想用代码明确一下,是不是这种情况:

class A
{
public:
virtual ~A() {}
};

class B
{
};

A* pa=new A();
B* pb=dynamic_cast&lt;B*&gt;(pa);


你说的编译可以通过,但运行可能会出错是这种情况吗?

还是:

class B
{
};

class A
{
};

A* pa=new A();
B* pb=dynamic_cast&lt;B*&gt;(pa);
#6
myajax952006-07-31 12:21

对,可能说的不是一个问题,应该所有编译器都是一样的,如果直接转换A*到B*一定会出编译错误,就算static_cast都会编译错误。但是我的那个例子中是从A*到A*,这个只有运行的时候才能查出来了,而dynamic_cast的检查方法是直接找vtable,所以可能crash。一定程度上讲dynamic_cast写的不是很结实。这个时候不应该给access vilotation,而应该是bad_cast exception。

B b;
A *p1,*p2;

p1 = (A *)(&b); // ok
// error p2 = static_cast<A *>(&b); //error。
p2 = static_cast<A *>(p1); // ok. but will have unexpected behavior.
p2 = dynamic_cast<A *>(p1); // ok, but may crash during run time.

#7
woodhead2006-07-31 12:47
dynamic_cast用在具有继承关系的类型转换。

T2 *p2 = new T2;
T1 *p1 = dynamic_cast<T1*>(p2);

T1是T2一个 可以访问的 基类。T1需要是多态的类型,要有虚函数。
如果不是这样,两个类型没有什么共同点,转换也没有什么意义。

返回0,还是抛出异常,要看是用的指针还是引用。
如果是引用,不能给引用赋0,所以抛出bad_case异常。


#8
myajax952006-07-31 12:49
是指针的时候不能保证给0,也能是exception。
#9
woodhead2006-07-31 13:06
我去查查哈。
#10
myajax952006-07-31 13:14
拿1楼或者6楼的例子试一下就知道了。
#11
michaelsoft2006-07-31 13:20
刚刚参考了一下bs的tcpl特别版:

dynamic_cast&lt;T*&gt;(p)

looks at the object pointed to by p (if any). If that object is of class T or has a unique base class of
type T, then dynamic_  cast returns a pointer of type T* to that object; otherwise, 0 is returned. If
the value of p is 0, dynamic_cast&lt;T*&gt;(p) returns 0.

A dynamic_cast requires a pointer or a reference to a polymorphic type to do a downcast or a
crosscast.

to myajax95:
从A*转换到A*,根本不需要在运行时,因为两个类型一样,直接把值赋过去就行,如:
A* p1=(A*)0x1;
A* p2=dynamic_cast&lt;A*&gt;(p1);
如果有运行时动作的话,肯定会产生access violation错误,但是实际没有。从反汇编可以看出来:
         A* p1=(A*)0x1;
00411A2E  mov         dword ptr [p1],1
    A* p2=dynamic_cast&lt;A*&gt;(p1);
00411A35  mov         eax,dword ptr [p1]
00411A38  mov         dword ptr [p2],eax

to woodhead:
只有向下类型转换的时候才会要求被转换者必须是多态类(至少有一个虚函数)。如果是向上类型转换,编译时就可确定,是用不着运行时判断的,如:
class A{};
class B : public A {};

B b;
A* pa=dynamic_cast&lt;A*&gt;(&amp;b);
反汇编:
         B b;
    A* pa=dynamic_cast&lt;A*&gt;(&amp;b);
00411A2E  lea         eax,[b]
00411A31  mov         dword ptr [pa],eax

to all:
运行时转换(借助于vtable)只会发生在向下类型转换时,如:
class A{};
class B : public A {};

A a;
B* pb=dynamic_cast&lt;B*&gt;(&amp;a);

由于借助了vtable,所以会要求A必须是多态类型,所以上述例子会编译失败。
而运行时的访问违规也只会发生在向下类型转换时,比如:
class A{ public: virtual~A() {} };
class B : public A {};

A* pa=(A*)0x1;
B* pb=dynamic_cast&lt;B*&gt;(pa);
反汇编:
         A* pa=(A*)0x1;
0041B1BE  mov         dword ptr [pa],1
    B* pb=dynamic_cast&lt;B*&gt;(pa);
0041B1C5  push        0   
0041B1C7  push        offset B `RTTI Type Descriptor' (455FB4h)
0041B1CC  push        offset A `RTTI Type Descriptor' (455FA0h)
0041B1D1  push        0   
0041B1D3  mov         eax,dword ptr [pa]
0041B1D6  push        eax  
0041B1D7  call        @ILT+3985(___RTDynamicCast) (419F96h)
0041B1DC  add         esp,14h
0041B1DF  mov         dword ptr [pb],eax
#12
myajax952006-07-31 13:52
明明是有access violation呀。家里没有VC2003,明天去公司试一下。
dynamic_cast不只看A*,而会继续查p1的info。

#13
woodhead2006-07-31 14:14
咱的说法确实有问题

引一段

Dynamic cast

1 The result of the expression dynamic_cast<T>(v) is the result of converting the expression v to type T. T shall be a pointer or reference to a complete class type, or “pointer to cv void”. Types shall not be defined in a dynamic_cast. The dynamic_cast operator shall not cast away constness (5.2.11).

2 If T is a pointer type, v shall be an rvalue of a pointer to complete class type, and the result is an rvalue of type T. If T is a reference type, v shall be an lvalue of a complete class type, and the result is an lvalue of the type referred to by T.

3 If the type of v is the same as the required result type (which, for convenience, will be called R in this description), or it is the same as R except that the class object type in R is more cvqualified than the class object type in v, the result is v (converted if necessary).

4 If the value of v is a null pointer value in the pointer case, the result is the null pointer value of type R.

5 If T is “pointer to cv1 B” and v has type “pointer to cv2 D” such that B is a base class of D, the result is a pointer to the unique B subobject of the D object pointed to by v. Similarly, if T is “reference to cv1 B” and v has type “cv2 D” such that B is a base class of D, the result is an lvalue for the unique B subobject of the D object referred to by v. In both the pointer and reference cases, cv1 shall be the same cvqualification as, or greater cvqualification than, cv2, and B shall be an accessible unambiguous base class of D.

[Example:
struct B {};
struct D : B {};
void foo(D* dp)
{
B* bp = dynamic_cast<B*>(dp); // equivalent to B* bp = dp;
}
—end example]

6 Otherwise, v shall be a pointer to or an lvalue of a polymorphic type .

7 If T is “pointer to cv void,” then the result is a pointer to the most derived object pointed to by v. Otherwise, a runtime check is applied to see if the object pointed or referred to by v can be converted to the type pointed or referred to by T.

8 The runtime check logically executes as follows:
— If, in the most derived object pointed (referred) to by v, v points (refers) to a public base class subobject of a T object, and if only one object of type T is derived from the subobject pointed (referred) to by v, the result is a pointer (an lvalue referring) to that T object.
— Otherwise, if v points (refers) to a public base class subobject of the most derived object, and the type of the most derived object has an unambiguous public base class of type T, the result is a pointer (an lvalue referring) to the T subobject of the most derived object.
— Otherwise, the runtime check fails.

9 The value of a failed cast to pointer type is the null pointer value of the required result type. A failed cast to reference type throws bad_cast (18.5.2).


[Example:
class A { virtual void f(); };
class B { virtual void g(); };
class D : public virtual A, private B {};
void g()
{
D d;
B* bp = (B*)&d; // cast needed to break protection
A* ap = &d; // public derivation, no cast needed
D& dr = dynamic_cast<D&>(*bp); // fails
ap = dynamic_cast<A*>(bp); // fails
bp = dynamic_cast<B*>(ap); // fails
ap = dynamic_cast<A*>(&d); // succeeds
bp = dynamic_cast<B*>(&d); // fails
}
class E : public D, public B {};
class F : public E, public D {};
void h()
{
F f;
A* ap = &f; // succeeds: finds unique A
D* dp = dynamic_cast<D*>(ap); // fails: yields 0
// f has two D subobjects
E* ep = (E*)ap; // illformed:
// cast from virtual base
E* ep1 = dynamic_cast<E*>(ap); // succeeds
}
—end example]

[此贴子已经被作者于2006-7-31 14:16:38编辑过]

#14
michaelsoft2006-07-31 14:18
vc6下是不是A必须是多态类才能编译通过啊?
#15
michaelsoft2006-08-01 08:32
我公司没有vc6,家里有,试了一下,在vc6里,尽管不是向下类型转换,也要求是多态类。看样子它对标准c++的支持还是不够。所以有没有访问违规出现的可能,还得看编译的时候要不要求是多态类,也就是依不依赖vtable了。
#16
myajax952006-08-01 09:00
用VC2003运行了一下,结果果然不crash。我再好好研究一下。
#17
lj_8606032006-08-02 11:12
95,偶来帮你顶!
#18
xinhang2006-08-03 01:01
顶上去! 以后再看!
#19
pulf2006-08-07 21:01
收藏了。
#20
myajax952006-08-18 13:36

查了一下ISO C++ standard 98 版和MSDN。懂了一些VC不同版本的问题,更新了顶楼的文章,大家有时间探讨一下。

[此贴子已经被作者于2006-8-19 7:15:35编辑过]

#21
woodhead2006-08-19 09:22
支持一个,我也试了试
[CODE]#include<iostream>
#include<fstream>
using namespace std;

class A{public: virtual void a(){}};
class B : public A {};
class C : public A {};
class D{public: virtual void d(){}};
class E : public B, public C, public D {};

int main()
{
C c;
C *pc = dynamic_cast<C*>((D*)&c);
if(pc==0)
cout<<"null"<<endl;
system("pause");
return 0;
}[/CODE]

用gcc的编译器,结果也是null。

我想,类型识别可能是 按照指针或引用的类型,把((D*)&c)看做D类型的指针,不管他以前是什么类型的
C和D的类型没有关系,如果从D到C的dynamic_cast应该是不行的。


#22
穆扬2006-08-19 10:25
提示: 作者被禁止或删除 内容自动屏蔽,只有管理员可见
#23
myajax952006-08-19 12:07
以下是引用woodhead在2006-8-19 9:22:01的发言:
C *pc = dynamic_cast<C*>((D*)&c);

用gcc的编译器,结果也是null。

我想,类型识别可能是 按照指针或引用的类型,把((D*)&c)看做D类型的指针,不管他以前是什么类型的
C和D的类型没有关系,如果从D到C的dynamic_cast应该是不行的。


问题在于
C *pc = dynamic_cast<C*>((D*)&e);
这里得到的结果不是NULL,也就是说,见了无关的指针,就先假设是crosscast,然后查指向的object的类型。e类型的是crosscast,所以非NULL,难道c类型是自己的类型,所以不是crosscast所以是NULL?

#24
woodhead2006-08-19 12:58
我上面又说错了哈,识别的时候会查看指针指向对象的具体类型,说是典型的实现方法是在类对象中加入类型信息,通过查type_info来确定,对于这些机制我了解的很少。

个人想法:
(D*)&c 和(D*)&e的转换不一样,(D*)&c是强制转换,(D*)&e是向上的安全转换,就跟D *pd = &e一样?
是不是(D*)&c以后C类型的信息丢失了,他最终查不出是C类型的。

#25
myajax952006-08-19 13:16
以下是引用woodhead在2006-8-19 12:58:16的发言:
我上面又说错了哈,识别的时候会查看指针指向对象的具体类型,说是典型的实现方法是在类对象中加入类型信息,通过查type_info来确定,对于这些机制我了解的很少。

个人想法:
(D*)&c 和(D*)&e的转换不一样,(D*)&c是强制转换,(D*)&e是向上的安全转换,就跟D *pd = &e一样?
是不是(D*)&c以后C类型的信息丢失了,他最终查不出是C类型的。

写成下面的样子得到的结果一样:
D* pc = (D*)&c, pe = (D*)&e; //作完这步两个指针还都合法,然后作dynamic_cast,结果一样。
可能是dynamic_cast把这种前面传来的C D这种不匹配的转换直接干掉了。我再查查ISO C++ Standard 10.3去。不过VC2003上的结果和gcc/VC2005不一样,看来是个错误。

#26
woodhead2006-08-19 15:30
C c;
D *pd = (D*)&c;
if(typeid( *pd ) == typeid(C) )
cout<<"same"<<endl;

是same,搞不清了,

觉得dynamic_cast主要是用在向下转换和交叉上的,其他的
#27
crystalthe2006-09-11 20:16

刚刚开始学习编程,也是刚刚来到这个论坛,感觉确实很好。

[此贴子已经被作者于2006-9-11 20:18:55编辑过]

#28
tancui2006-12-19 21:03

什么与什么呀,不懂

#29
peswe2006-12-19 21:28

哎,我也是呢,还没开始看就已经晕过去了!!!!

#30
lc26106242006-12-23 21:21
同感......
#31
yuyunliuhen2006-12-23 21:35
哈哈 ,好啊,谢谢斑竹!
#32
Alome2007-01-14 17:16
啊~
看不太懂~真是笨笨的,不好意思,不过谢谢
#33
newCpp2009-08-17 07:13
先顶一下下再说。好像蛮不错的呀
1