網絡上有很多討論C++ 的“引用”與“指針“的區別的文章,談到區別,其中有一條:“引用不能為空(NULL),引用必須與合法的存儲單元關聯,指針則可以是NULL)”,但在實際應用中,有時候為了保持一致性,我們會拋開這個規則,人為創造出一個“空引用”。
很多情況下,“空引用”確實可以工作,以致於“引用不能為空”的忠告,被嘲笑為形式主義,僅僅是標准制定者的聳人聽聞。一個“空引用”的例子是:
[cpp]
int * a = NULL;
int & b = *a;
int * a = NULL;
int & b = *a;於是當訪問b的時候,程序異常出現了:
[cpp]
void f(int & p)
{
p = 0;
}
f(b);
void f(int & p)
{
p = 0;
}
f(b);當然,可以增加點判斷,修正這個問題:
[cpp]
void f(int & p)
{
if (&p) p = 0;
}
void f(int & p)
{
if (&p) p = 0;
}
怎麼樣,是不是有點別扭?但是如果換成成指針,你要輸入的字符數是一模一樣的:
[cpp]
void f(int * p)
{
if (p) *p = 0;
}
void f(int * p)
{
if (p) *p = 0;
} 於是,到底是使用“引用”還是“指針”,好像就是智者見智仁者見仁的事情了。
然而,然而。。。。。。
這真的一樣嗎?
我們來看看復雜一點的例子:
[cpp]
// test.cpp
#include <iostream>
class A
{
int a;
};
class B
{
int b;
};
class C
: public A, public B
{
int c;
};
void fb(B & b)
{
std::cout << &b << std::endl;
}
void fb(B * b)
{
std::cout << b << std::endl;
}
int main(int argc, char* argv[])
{
C * c = NULL;
fb(c);
fb(*c);
return 0;
}
// test.cpp
#include <iostream>
class A
{
int a;
};
class B
{
int b;
};
class C
: public A, public B
{
int c;
};
void fb(B & b)
{
std::cout << &b << std::endl;
}
void fb(B * b)
{
std::cout << b << std::endl;
}
int main(int argc, char* argv[])
{
C * c = NULL;
fb(c);
fb(*c);
return 0;
}
編譯運行一下看看:
[plain]
$ ./test
0
0x4
$ ./test
0
0x4咦,怎麼&b不是0,也就是不是“空引用”了,這時候,即使加上判斷,if (&b),也無濟於事了。
大家也許注意到了,上面是linux環境運行,那麼windows環境呢:
[plain]
>test.exe
00000000
00000000
>test.exe
00000000
00000000這時候,“空引用”保持了他的“空”屬性,僅在windows平台做C++的開發者,可以松口氣了。
這是怎麼回事呢,是你的眼睛欺騙了你?也許是,但是CPU不會欺騙我們,從匯編代碼可以看出本質。下面是linux平台編譯的代碼:
[plain]
Dump of assembler code for function main:
0x0804870a <+0>: push %ebp
0x0804870b <+1>: mov %esp,%ebp
0x0804870d <+3>: and $0xfffffff0,%esp
0x08048710 <+6>: sub $0x20,%esp
0x08048713 <+9>: movl $0x0,0x1c(%esp)
0x0804871b <+17>: cmpl $0x0,0x1c(%esp)
0x08048720 <+22>: je 0x804872b <main+33>
0x08048722 <+24>: mov 0x1c(%esp),%eax
0x08048726 <+28>: add $0x4,%eax
0x08048729 <+31>: jmp 0x8048730 <main+38>
0x0804872b <+33>: mov $0x0,%eax
0x08048730 <+38>: mov %eax,(%esp)
0x08048733 <+41>: call 0x80486df <fb(B*)>
0x08048738 <+46>: mov 0x1c(%esp),%eax
0x0804873c <+50>: add $0x4,%eax
0x0804873f <+53>: mov %eax,(%esp)
0x08048742 <+56>: call 0x80486b4 <fb(B&)>
0x08048747 <+61>: mov $0x0,%eax
0x0804874c <+66>: leave
0x0804874d <+67>: ret
Dump of assembler code for function main:
0x0804870a <+0>: push %ebp
0x0804870b <+1>: mov %esp,%ebp
0x0804870d <+3>: and $0xfffffff0,%esp
0x08048710 <+6>: sub $0x20,%esp
0x08048713 <+9>: movl $0x0,0x1c(%esp)
0x0804871b <+17>: cmpl $0x0,0x1c(%esp)
0x08048720 <+22>: je 0x804872b <main+33>
0x08048722 <+24>: mov 0x1c(%esp),%eax
0x08048726 <+28>: add $0x4,%eax
0x08048729 <+31>: jmp 0x8048730 <main+38>
0x0804872b <+33>: mov $0x0,%eax
0x08048730 <+38>: mov %eax,(%esp)
0x08048733 <+41>: call 0x80486df <fb(B*)>
0x08048738 <+46>: mov 0x1c(%esp),%eax
0x0804873c <+50>: add $0x4,%eax
0x0804873f <+53>: mov %eax,(%esp)
0x08048742 <+56>: call 0x80486b4 <fb(B&)>
0x08048747 <+61>: mov $0x0,%eax
0x0804874c <+66>: leave
0x0804874d <+67>: ret
這是windows平台的:
[plain]
wmain:
004114D0 push ebp
004114D1 mov ebp,esp
004114D3 sub esp,0DCh
004114D9 push ebx
004114DA push esi
004114DB push edi
004114DC lea edi,[ebp-0DCh]
004114E2 mov ecx,37h
004114E7 mov eax,0CCCCCCCCh
004114EC rep stos dword ptr es:[edi]
004114EE mov dword ptr [c],0
004114F5 mov eax,dword ptr [c]
004114F8 mov dword ptr [rc],eax
004114FB cmp dword ptr [c],0
004114FF je wmain+3Fh (41150Fh)
00411501 mov eax,dword ptr [c]
00411504 add eax,4
00411507 mov dword ptr [ebp-0DCh],eax
0041150D jmp wmain+49h (411519h)
0041150F mov dword ptr [ebp-0DCh],0
00411519 mov ecx,dword ptr [ebp-0DCh]
0041151F push ecx
00411520 call fb (411118h)
00411525 add esp,4
00411528 cmp dword ptr [rc],0
0041152C je wmain+6Ch (41153Ch)
0041152E mov eax,dword ptr [rc]
00411531 add eax,4
00411534 mov dword ptr [ebp-0DCh],eax
0041153A jmp wmain+76h (411546h)
0041153C mov dword ptr [ebp-0DCh],0
00411546 mov ecx,dword ptr [ebp-0DCh]
0041154C push ecx
0041154D call fb (41108Ch)
00411552 add esp,4
00411555 xor eax,eax
00411557 pop edi
00411558 pop esi
00411559 pop ebx
0041155A add esp,0DCh
00411560 cmp ebp,esp
00411562 call @ILT+345(__RTC_CheckEsp) (41115Eh)
00411567 mov esp,ebp
00411569 pop ebp
0041156A ret
wmain:
004114D0 push ebp
004114D1 mov ebp,esp
004114D3 sub esp,0DCh
004114D9 push ebx
004114DA push esi
004114DB push edi
004114DC lea edi,[ebp-0DCh]
004114E2 mov ecx,37h
004114E7 mov eax,0CCCCCCCCh
004114EC rep stos dword ptr es:[edi]
004114EE mov dword ptr [c],0
004114F5 mov eax,dword ptr [c]
004114F8 mov dword ptr [rc],eax
004114FB cmp dword ptr [c],0
004114FF je wmain+3Fh (41150Fh)
00411501 mov eax,dword ptr [c]
00411504 add eax,4
00411507 mov dword ptr [ebp-0DCh],eax
0041150D jmp wmain+49h (411519h)
0041150F mov dword ptr [ebp-0DCh],0
00411519 mov ecx,dword ptr [ebp-0DCh]
0041151F push ecx
00411520 call fb (411118h)
00411525 add esp,4
00411528 cmp dword ptr [rc],0
0041152C je wmain+6Ch (41153Ch)
0041152E mov eax,dword ptr [rc]
00411531 add eax,4
00411534 mov dword ptr [ebp-0DCh],eax
0041153A jmp wmain+76h (411546h)
0041153C mov dword ptr [ebp-0DCh],0
00411546 mov ecx,dword ptr [ebp-0DCh]
0041154C push ecx
0041154D call fb (41108Ch)
00411552 add esp,4
00411555 xor eax,eax
00411557 pop edi
00411558 pop esi
00411559 pop ebx
0041155A add esp,0DCh
00411560 cmp ebp,esp
00411562 call @ILT+345(__RTC_CheckEsp) (41115Eh)
00411567 mov esp,ebp
00411569 pop ebp
0041156A ret
匯編代碼有興趣自己研究,不細說了。
回過頭想想,兩個平台的編譯器的兩種處理方式,都有他的合理性,windows平台增加了容錯性,而linux平台在處理引用時減少判斷,增加性能。這隱隱體現出windows與linux開發理念的不同。
最後,請大家記住,引用不能為空,如果可能存在空對象時,請使用指針。