淺談指針的比較

1、前言

有人說指針是C語言的靈魂,也有人說沒學好指針就等於不會C語言。html

雖然在現代C++中通常都是推薦儘可能避免使用原生的raw指針,而是以smart pointer 和reference替代之。可是不管怎樣,對於C/C++來講,指針始終是個繞不過去的坎。究其緣由,是由於C/C++都是支持面向底層操做的語言,而 面向底層操做就得能操縱內存,這個時候就須要指針了。爲何呢?我的以爲指針實際上就是對機器語言/ASM中的經過虛擬地址操做內存的這一行爲的一種抽 象。linux

例如ide

movl %eax, (%edx)

將寄存器eax中的值寫入內存地址爲寄存器edx的值的內存中。若是把edx看作一個指針的話,也就至關於函數

*p_edx = value_eax

2、指針的比較

關於指針,有着許多技巧和用途,後文主要談談關於C++中指針比較操做的一些容易踏入的坑。spa

先來看看這段代碼指針

 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 }

輸出的結果是:orm

pb = 0028FEE0htm

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 castif (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

3、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 意義下它們是相等的。

4、總結

  • 指針之間的比較只能是指針的靜態類型相同,或者同屬於同一繼承層次且其中一個指針的靜態類型爲所指對象的類型。

  • 指針的靜態類型相同時,比較的是地址的值的大小。

  • 指針的靜態類型不一樣,可是同屬於同一繼承對象,而且其中一個指針的靜態類型爲所指對象的類型時,比較的是兩指針是否指向同一對象。如果指向同一對象,則兩指針相等;若不是指向同一對象,則比較指針的地址值的大小。

  • 智能指針shared_ptr/weak_ptr的onwer_before成員函數描述的是:當比較的2個智能指針的類型屬於同一繼承層次時表現爲「相等」的意義;當2個只能指針的類型不屬於同一繼承層次時,比較的是指針的地址值的大小。

(完)

相關文章
相關標籤/搜索