浅谈指针的比较

一、前言

  有人说指针是C语言的灵魂,也有人说没学好指针就等于不会C语言。

  虽然在现代C++中一般都是推荐尽量避免使用原生的raw指针,而是以smart pointer 和reference替代之。但是无论怎样,对于C/C++来说,指针始终是个绕不过去的坎。究其原因,是因为C/C++都是支持面向底层操作的语言,而面向底层操作就得能操纵内存,这个时候就需要指针了。为什么呢?个人觉得指针实际上就是对机器语言/ASM中的通过虚拟地址操作内存的这一行为的一种抽象。

  例如

movl %eax, (%edx)

  将寄存器eax中的值写入内存地址为寄存器edx的值的内存中。如果把edx看做一个指针的话,也就相当于

*p_edx = value_eax

二、指针的比较

  关于指针,有着许多技巧和用途,后文主要谈谈关于C++中指针比较操作的一些容易踏入的坑。

  先来看看这段代码

 1 class BaseA
 2 {
 3 public:
 4     int a;
 5 };
 6
 7 class BaseB
 8 {
 9 public:
10     double b;
11 };
12
13 class Derived : public BaseA, public BaseB
14 {
15 };
16
17 int main(int argc, char const *argv[])
18 {
19     Derived derivd;
20     Derived* pd = &derivd;
21     BaseB* pb = &derivd;
22     printf("pb = %p\n", pb);
23     printf("pd = %p\n", pd);
24     if (pb == pd)
25     {
26         printf("pb == pd\n");
27     }
28     else
29     {
30         printf("pb != pd\n");
31     }
32 }

  输出的结果是:

  pb = 0028FEE0
  pd = 0028FED8
  pb == pd

  可以看到指针pb和pd值并不一样,但是编译器却认为他们相等,为什么呢?

  1.  当2个指针的静态类型以及所指对象的类型都属于同一个继承层次结构中,并且其中一个指针类型是所指对象的静态类型的时候,指针的比较,实际上比较的是两个指针是否指向同一个对象。

  若2个指针指向同一个对象,被编译器决议为相等。编译器在比较的时候加上适当的offset值,例如上面的情况,相当于在比较的时候编译器做了这样的改动:

if(pd == (pb - sizeof(int))

  若2个指针指向不同的对象,就被决议为不相等,并且比较的是指针保存的地址的值的大小。

 1 int main(int argc, char const *argv[])
 2 {
 3     Derived derived1;
 4     Derived derived2;
 5     Derived* pd = &derived1;
 6     BaseB* pb = &derived2;
 7     printf("%p\n", pd);
 8     printf("%p\n", pb);
 9     if (pd < pb)
10     {
11         printf("pd < pb\n");
12     }
13     else if (pd == pb)
14     {
15         printf("pd == pb\n");
16     }
17     else
18     {
19         printf("pd > pb\n");
20     }
21 }

  得到的结果为: 

  0028FED8
  0028FED0
  pd > pb

  2.  当2个指针的静态类型不属于同一个继承层次结构中,但是2个指针都指向同一个对象的时候,该比较是违法行为,编译器会报编译期错误

 1 int main(int argc, char const *argv[])
 2 {
 3     Derived derivd;
 4     Derived* pd = &derivd;
 5     int* pb = reinterpret_cast<int*>(&derivd);
 6     printf("pb = %p\n", pb);
 7     printf("pd = %p\n", pd);
 8     if (pb == pd)
 9     {
10         printf("pb == pd\n");
11     }
12     else
13     {
14         printf("pb != pd\n");
15     }
16 }

  编译器报错为:

error: comparison between distinct pointer types ‘int*‘ and ‘Derived*‘ lacks a cast [-fpermissive]
if (pb == pd)

  3.  当2个指针的静态类型以及所指对象类型都属于同一个继承层次结构,但是2个指针的静态类型都不是所指对象的类型时,该比较是违法行为,编译器会报编译期错误:

 1 int main(int argc, char const *argv[])
 2 {
 3     Derived derivd;
 4     BaseB* pb = &derivd;
 5     BaseA* pa = &derivd;
 6     printf("pb = %p\n", pb);
 7     printf("pd = %p\n", pa);
 8     if (pb == pa)
 9     {
10         printf("pb == pa\n");
11     }
12     else
13     {
14         printf("pb != pa\n");
15     }
16 }

  编译器报错为:

error: comparison between distinct pointer types ‘BaseB*‘ and ‘BaseA*‘ lacks a cast
if (pb == pa)

  另外一些其他的行为,例如2个指针的类型不同但同属于一个继承层次,然后通过强制类型转换让他们俩都指向一个不属于该继承层次的对象,这样的行为都是为未定义行为,也许编译器不会报编译期错误,但结果是未定义的,可能是任何结果。

  可能有人会说,什么时候指针比较的是他们所保存的地址的值呢呢?

  答案是当2个指针的静态类型相同的时候:

 1 int main(int argc, char const *argv[])
 2 {
 3     Derived derived1;
 4     Derived derived2;
 5     Derived* p1 = &derived1;
 6     Derived* p2 = &derived2;
 7     if (p1 < p2)
 8     {
 9         printf("p1 < p2\n");
10     }
11     else if (p1 == p2)
12     {
13         printf("p1 == p2\n");
14     }
15     else
16     {
17         printf("p1 > p2\n");
18     }
19 }

  结果为:p1 > p2

三、shared_ptr的owner_before

  boost::shared_ptr/std::shared_ptr中有一个owner_before成员函数,原型为

template <class U> bool owner_before (const shared_ptr<U>& x) const;
template <class U> bool owner_before (const weak_ptr<U>& x) const;

  当该shared_ptr和x的类型同属一个继承层次时,不管他们类型是否相同,他们两都被决议为“相等”。当他们的类型不属于同一继承层次时,比较的为他们所管理指针的地址值的大小。

 1 int main()
 2 {
 3     boost::shared_ptr<Derived> pd(new Derived);
 4     boost::shared_ptr<BaseB> pb(pd);
 5     printf("%p %p\n", pd.get(), pb.get());
 6     printf("%d %d\n", pd < pb, pb < pd);  // 0 0
 7     printf("%d %d\n", pd.owner_before(pb), pb.owner_before(pd));  // 0 0
 8     boost::shared_ptr<void> p0(pd), p1(pb);
 9     printf("%p %p\n", p0.get(), p1.get());
10     printf("%d %d\n", p0.get() < p1.get(), p1.get() < p0.get());  // 1 0
11     printf("%d %d\n", p0.owner_before(p1), p1.owner_before(p0));  // 0 0
12 }

  为什么shared_ptr会提供这样的成员函数呢?

  因为一个智能指针有可能指向了另一个智能指针中的某一部分,但又要保证这两个智能指针销毁时,只对那个被指的对象完整地析构一次,而不是两个指针分别析构一次。

  在这种情况下,指针就可以分为两种,一种是 stored pointer 它是指针本身的类型所表示的对象(可能是一个大对象中的一部分);另一种是 owned pointer 指向内存中的实际完整对象(这一个对象可能被许多智能指针指向了它里面的不同部分,但最终只析构一次)。owner-based order 就是指后一种情况,如果内存中只有一个对象,然后被许多 shared pointer 指向了其中不同的部分,那么这些指针本身的地址肯定是不同的,也就是operator<()可以比较它们,并且它们都不是对象的 owner,它们销毁时不会析构对象。但它们都指向了一个对象,在owner-based order 意义下它们是相等的。

四、总结

  • 指针之间的比较,要么指针的静态类型相同,要么同属于同一继承层次且其中一个指针的静态类型为所指对象的类型。
  • 指针的静态类型相同时,比较的是地址的值的大小。
  • 指针的静态类型不同,但是同属于同一继承对象,并且其中一个指针的静态类型为所指对象的类型时,比较的是两指针是否指向同一对象。若是指向同一对象,则两指针“相等”;若不是指向同一对象,则比较指针的地址值的大小。
  • 智能指针shared_ptr/weak_ptr的onwer_before成员函数描述的是:当比较的2个智能指针的类型属于同一继承层次时表现为“相等”的含义;当2个智能指针的类型不属于同一继承层次时,比较的是所管理指针的地址值的大小。

(完)

时间: 2024-08-08 13:57:14

浅谈指针的比较的相关文章

浅谈指针的偏移

记得当初学习指针的时候,总是把指针和地址混为一谈,总以为说到指针就是指某个地址而已,后来加强对各类指针的认识以后,才认识到指针不止是简单的地址. 指针是一种类型,通过类型可以声明一个变量并保存一个内存地址,不同类型的指针对保存的地址会有不同的解释,编译器根据指针的类型(对应的偏移量)解引用出相应的数据. 首先在32位程序设计里,指针大小为4bytes,满足2^32 寻址范围. 到底偏移多少: 曾经探究过一个问题,代码如下: int a[4][2] = { 0, 1, 2, 3, 4, 5 ,7,

浅谈指针

指针:一个用来存储数据存储地址的变量. int a=10;  a变量里面存储的数据0 1按照int 的长度解析为数据 int *p=&a; p变量里面存储的数据0 1解析为地址,只是这个地址(这个地址是这段空间的首地址)标号的那段空间里面存储的是a的值,这里的p是给这段地址的取的名字用来给程序员看的,这里的a是给这段地址里面存储内容所取得名字来给程序员看的.

【C语言】 浅谈指针

指针是就是地址,是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址. 要搞清一个指针需要搞清指针的四方面的内容:指针的类型.指针所指向的 类型.指针的值或者叫指针所指向的内存区.指针本身所占据的内存区.让我们分别说明. 首先,先罗列出几种常见的类型: int p;   //这是一个普通的整型变量int *p;  //首先从P处开始,先与*结合,所以说明P是一个指针,然后再与int结合,说明指针所指向的内容的类型为int型.所以P是一个返回整形数据的指针 int p[3];  //首先从

浅谈指针和引用

我们知道用指针和引用来定义函数形参的时候,都可以直接改变参的值.那么指针和引用有哪些区别呢? 我们先根据引用和指针的定义展开:引用是某个变量或者对象的别名,而指针则存储的是一个机器码地址,这个地址是某个具体变量或者对象的地址.因此区别有: 1)指针可以为空,但是引用不行 2)声明指针可以不指向任何对象,因此使用指针之前必须做判空操作,而引用则不必 3)引用一旦声明后,就不可以改变指向:但是指针可以,如++操作符,指针则指向下一个对象,而引用则改变的是指向对象的内容 4)引用的大小是所指变量的大小

Go浅谈指针

一:指针的定义 简单来讲,指针就是存放变量的地址,通过"*"号来读取地址里面存放的值 二:举例说明 由上图可知,由于指针指向的是内存地址,即变量存储值得真实地址,所以修改指针,即修改原变量数值. 原文地址:https://www.cnblogs.com/louis181214/p/10197785.html

浅谈RAII&智能指针

关于RAII,官方给出的解释是这样的"资源获取就是初始化".听起来貌似不是很懂的哈,其实说的通俗点的话就是它是一种管理资源,避免内存泄漏的一种方法.它可以保证在各种情况下,当你对对象进行使用时先通过构造函数来进行资源的分配和初始化,最后通过析构函数来进行清理,有效的保证了资源的正确分配和释放.(特别是在异常中,因为异常往往会改变代码正确的执行顺序,这就很容易引起资源管理的混乱和内存的泄漏) 其中智能指针就是RAII的一种实现模式,所谓的智能就是它可以自动化的来管理它所指向那份空间的资源

转: 浅谈C/C++中的指针和数组(二)

转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242419.html 浅谈C/C++中的指针和数组(二) 前面已经讨论了指针和数组的一些区别,然而在某些情况下,指针和数组是等同的,下面讨论一下什么时候指针和数组是相同的. C语言标准对此作了说明: 规则1:表达式中的数组名被编译器当做一个指向该数组第一个元素的指针: 注:下面几种情况例外 1)数组名作为sizeof的操作数 2)使用&取数组的地址 规则2:下标总是与指针的偏移量

转:浅谈C/C++中的指针和数组(一)

转自:http://www.cnblogs.com/dolphin0520/archive/2011/11/09/2242138.html 浅谈C/C++中的指针和数组(一) 指针是C/C++的精华,而指针和数组又是一对欢喜冤家,很多时候我们并不能很好的区分指针和数组,对于刚毕业的计算机系的本科生很少有人能够熟练掌握指针以及数组的用法和区别.造成这种原因可能跟现在大学教学以及现在市面上流行的很多C或者C++教程有关,这些教程虽然通俗易懂,但是在很多关键性的地方却避而不谈或者根本阐述不清楚,甚至很

浅谈为什么只有指针能够完成多态及动态转型的一个误区

c++多态由一个函数地址数组Vtable和一个指向Vtable的指针vptr实现. 具体来说,类拥有自己的vtable,类的vtable在编译时刻完成. 每个对象有自己的vptr指针,该指针初始化时指向对象所实现的类的vtable. 关于向上转型的误区: 通常对于向上转型的理解是这样的,当子类对象向上转型(允许隐式)成父类对象时,实际上只是将子类对象暂时看做父类对象,内部的数据并未改变. 对于没有虚函数的对象,这句话是正确的,但是,当引入虚函数后,这样的理解是有问题的,实际上,向上转型的过程中,