先說些題外話,今天學習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)當成可修改的類型,試圖修改,在代碼中沒法檢測出來,可是運行時就會報錯。可是把一個可修改的類型當成不可修改的類型,試圖修改,編譯時就會報錯,而且這是咱們指望的。
打個也許不太恰當的比方,如今眼前有個鑽石,可是也有可能只是想塊鑽石的糖。
一、若是它是鑽石,你把它當成糖,用力咬下去牙齒就碎了。
二、若是它是糖,你把它當成鑽石,你就不會去咬,牙齒仍是無缺的,至少避免了不當心咬到鑽石的危險。