理解C/C++中const char*、char* const、const char* const、char* const*等等

先說些題外話,今天學習execve(2)的使用,因爲書上代碼使用的是C89標準,因此下面這種代碼都被我修改了安全

char* s[] = { "aaa", "bbb", "cc" };

也就是在char前面加個const,由於"aaa"、"bbb"、"cc"都是字符串字面值(string literal),在C++標準中string literal只能轉換成const char*,緣由是即便用char*指向string literal,也是沒法修改的。好比上述代碼不作修改在舊標準中是可行的,可是妄圖用s[0][0] = 'd'來使s[0]變成"daa",那麼運行時會報錯,由於string literal是存在靜態常量區的,不可修改,可是能夠取得string literal的地址(用指針類型表示)。這就跟char*的語義產生了衝突,由於char*指向的是char而不是const char,理論上是能夠賦值的。學習

因而當我改爲const char* s[]後,傳入execve(2)時編譯報錯:期待參數類型是char * const*,可是傳入參數類型是const char **。測試

int execve(const char *filename, char *const argv[],
                  char *const envp[]);

當我去掉const(也就是變回了char* s[])後,編譯經過。從例子能夠看出,char**能夠轉換成char* const*,可是const char**不能顯式轉換成char* const*,這樣的規則除了像我這樣實際修改代碼測試,還有什麼辦法記下來呢?spa

這個問題博客最後再回答,先出道題:如下類型的變量p,分別具備什麼性質?指針

具體而言,p、p[0]、p[0][0]……是否能夠修改(即而出p = q、p[0] = q這樣的代碼),像p[1]、p[2]……和p[0]類型是相同的,性質也相同,code

(1)const char* p (2)char const* p (3)char* const p (4)const char** p (5)char const** (6)char* const* (7)char** constblog

思路簡單來講就是「就近原則」+「符號*的消去」+「const性質」,具體看我下面解釋字符串

(1)const char* p:離p最近的是*而不是const,所以p能夠修改;如今考慮p[0],那麼,對一個指針p使用運算符*,操做步驟是消去*到p之間的全部部分,那麼*p就變成了const char類型,被const修飾,所以不能夠修改博客

驗證代碼string

char s1[] = { 'a', 'b', 'c' };
char s2[] = { 'a', 'b', 'c' };
const char* p = s1;
p[0] = 'd';  // 編譯報錯
p = s2;      // 編譯經過

(2)char const* p:離p最近的是*而不是const,所以p能夠修改;考慮p[0],消去*後爲char const,被const修飾,所以不能夠修改。結果和(1)同樣

(3)char* const p:離p最近的是const而不是*,所以p不能夠修改;考慮p[0],消去* const部分剩下的是char,能夠修改。

驗證代碼:把(1)的const char* p = s1;改爲char* const p = s1;

(4)const char** p :離p最近的是*而不是const,所以p能夠修改;考慮p[0],消去*後剩下const char*,距離最近的是*而不是const,所以p[0]能夠修改;考慮p[0][0],消去*後剩下const char,不可修改;

驗證代碼

char s1[] = { 'a', 'b', 'c', '\0' };
char s2[] = { 'd', 'e', 'f', '\0' };
char* ss1[] = { &s1[0], &s2[0] };
char* ss2[] = { &s1[0], &s2[0] };
const char** p = (const char**)ss1;  // 須要強制轉換
p = (const char**)ss2; // 編譯經過
p[0] = &s2[0];         // 編譯經過
p[0][0] = 'x';         // 編譯報錯

(5)char const** p:同(4)

(6)char* const* p:離p最近的是*而不是const,所以p能夠修改;考慮p[0],消去*後剩下char* const,距離最近的是const而不是*,所以p[0]不可修改;考慮p[0][0],消去* const後剩下char,可修改;

驗證代碼

char* const* p = ss1;
p = ss2;           // 編譯經過
p[0] = &s1[0];     // 編譯報錯
p[0][0] = 'x';     // 編譯經過

(7)char** const p:離p最近的是const而不是*,所以p不能夠修改;考慮p[0],消去* const後剩下char*,距離最近的是*,所以p[0]能夠修改;考慮p[0][0],消去*後剩下char,可修改

驗證代碼

char** const p = ss1;
p = ss2;           // 編譯報錯
p[0] = &s1[0];     // 編譯經過
p[0][0] = 'x';     // 編譯經過

總結下來,假設變量p,把p的類型看作字符串s(好比"const char*"),因爲變量聲明/定義時寫做const char* p;所以把s最右邊的看做當初距離p最近的

分析字符串s時忽略空格部分,規律以下:

(1)若s最右邊的是const而不是*,則p不可修改;不然p可修改。

(2)p[0]的類型字符串只須要找到s最右邊的字符*,而後去掉該字符之後(包括該字符)的部分,獲得p[0]的類型字符串。

 

那麼,再來解決以前的問題吧,爲何const char**不能顯式轉換成char* const*?

先來考慮char* const*的意義,設該類型的變量爲p,用上述方法可得出:p可修改,p[0]不可修改,p[0][0]可修改。

而對const char** p而言:p可修改,p[0]可修改,p[0][0]不可修改。

把可修改的類型轉換成不可修改的類型是安全的,可是把自己不可修改的類型轉換成可修改的類型就是危險的。類型轉換並不改變變量的本質,若是把一個不可修改的類型(好比以前提到的string literal)當成可修改的類型,試圖修改,在代碼中沒法檢測出來,可是運行時就會報錯。可是把一個可修改的類型當成不可修改的類型,試圖修改,編譯時就會報錯,而且這是咱們指望的。

打個也許不太恰當的比方,如今眼前有個鑽石,可是也有可能只是想塊鑽石的糖。

一、若是它是鑽石,你把它當成糖,用力咬下去牙齒就碎了。

二、若是它是糖,你把它當成鑽石,你就不會去咬,牙齒仍是無缺的,至少避免了不當心咬到鑽石的危險。

相關文章
相關標籤/搜索