什麼是型別安全?
型別安全很大程度上可以等價於記憶體安全,型別安全的代碼不會試圖訪問自己沒被授權的記憶體地區。“型別安全”常被用來形容程式設計語言,其根據在於該門程式設計語言是否提供保障型別安全的機制;有的時候也用“型別安全”形容某個程式,判別的標準在於該程式是否隱含類型錯誤。型別安全的程式設計語言與型別安全的程式之間,沒有必然聯絡。好的程式員可以使用類型不那麼安全的語言寫出類型相當安全的程式,相反的,差一點兒的程式員可能使用類型相當安全的語言寫出類型不太安全的程式。絕對型別安全的程式設計語言暫時還沒有。
C語言的型別安全
C只在局部上下文中表現出型別安全,比如試圖從一種結構體的指標轉換成另一種結構體的指標時,編譯器將會報告錯誤,除非使用顯式類型轉換。然而,C中相當多的操作是不安全的。以下是兩個十分常見的例子:
(1)printf格式輸出
/* - print.cpp * version:1.1 */int main(){printf("%d\n",10);system("pause");return 0;}
上面的代碼很簡單,printf函數中,%d與10匹配,結果正確。
稍作修改:
/* - print.cpp * version:1.2 */int main(){printf("%f\n",10);system("pause");return 0;}
%f浮點數與10並不匹配,但是編譯通過,執行也沒報錯,但是結果卻是:
0.000000
請按任意鍵繼續. . .
更進一步,把%f修改為%s,編譯通過,執行將報錯Access Violation。
(2)malloc函數的傳回值
malloc是C中進行記憶體配置的函數,它的傳回型別是void*即空類型指標,常常有這樣的用法char* pStr=(char*)malloc(100*sizeof(char)),這裡明顯做了顯式的類型轉換。類型匹配尚且沒有問題,但是一旦出現int* pInt=(int*)malloc(100*sizeof(char))就很可能帶來一些問題,而這樣的轉換C並不會提示錯誤。
C++的型別安全
如果C++使用得當,它將遠比C更有型別安全。相比於C,C++提供了一些新的機制保障型別安全:
(1)操作符new返回的指標類型嚴格與對象匹配,而不是void*;
(2)C中很多以void*為參數的函數可以改寫為C++模板函數,而模板是支援類型檢查的;
(3)引入const關鍵字代替#define constants,它是有類型、有範圍的,而#define constants只是簡單的文本替換;
(4)一些#define宏可被改寫為inline函數,結合函數的重載,可在型別安全的前提下支援多種類型,當然改寫為模板也能保證型別安全;
(5)C++提供了dynamic_cast關鍵字,使得轉換過程更加安全,因為dynamic_cast比static_cast涉及更多具體的類型檢查。
即便如此,C++也不是絕對型別安全的程式設計語言。如果使用不得當,同樣無法保證型別安全。比如下面兩個例子:
int i=5;void* pInt=&i;double d=(*(double*)pInt);cout<<d<<endl;
輸入結果不是5,而意想不到的結果:-9.25596e+061。又比如:
#include<iostream>using namespace std;class Parent{};class Child1:public Parent{public:int i;Child1(int e):i(e){}};class Child2:public Parent{public:double d;Child2(double e):d(e){}};int main(){Child1 c1(5);Child2 c2(4.1);Parent* pp;Child1* pc1;pp=&c1;pc1=(Child1*)pp; //#1 強制轉換,由於類型仍然為Child1*,不造成錯誤cout<<pc1->i<<endl;pp=&c2;pc1=(Child1*)pp; //#2 強制轉換,且類型發生變化,將造成錯誤cout<<pc1->i<<endl;system("pause");return 0;}
結果如下:
5
1717986918
請按任意鍵繼續. . .
上面兩個例子之所以引起類型不安全的問題,是因為程式員使用不得當。第一個例子用到了空類型指標void*,第二個例子則是在兩個類型指標之間進行強制轉換。因此,想保證程式的型別安全,應盡量避免使用空類型指標void*,盡量不對兩種類型指標做強制轉換。