const char*, char const*, char*const的區別問題,Bjarne在他的The C++ Programming Language裏面給出過一個助記的方法:
把一個聲明從右向左讀。 php
char * const cp; ( * 讀成 pointer to ) linux
cp is a const pointer to char程序員
cp 是一個指向char的常量指針編程
const char * p;
ubuntu
p is a pointer to const char;數組
p是一個指向常量char的指針ide
char const * p;函數
同上由於C++裏面沒有const*的運算符,因此const只能修飾屬於前面的類型char,所以char const * p等價於const char *p。
C++標準規定,const關鍵字放在類型或變量名以前等價的。工具
深刻理解const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p測試
1、可能的組合:
(1)const char*p
(2)char const*p
(3)char *const p
(4)const char **p
(5)char const**p
(6)char *const *p
(7)char **const p
固然還有在(5)、(6)、(7)中再插入一個const的若干狀況,不過度析了以上7中,其餘的就可類推了!
2、理解助記法寶:
1。關鍵看const 修飾誰。
2。因爲沒有 const *的運算,若出現 const * 的形式,則const其實是修飾前面的。
好比:char const*p,因爲沒有const*運算,則const其實是修飾前面的char,所以char const*p等價於const char*p。也就是說上面7種狀況中,(1)和(2)等價。 同理,(4)和(5)等價。在(6)中,因爲沒有const*運算,const實際上修飾的是前面的char*,但不能在定義時轉換寫成 const(char *)*p,由於在定義是"()"是表示函數。
3、深刻理解7種組合
(0)程序 在執行時爲其開闢的空間皆在內存(RAM)中,而RAM裏的內存單元是可讀可寫 的;指針只是用來指定或定位要操做的數據的工具,只是用來讀寫RAM裏內存單元的工做指針 。若對指針不加任何限定,程序中一個指針能夠指向RAM中的任意位置(除了系統敏感區,如操做系統內核所在區域)並對其指向的內存單元進行讀和寫操做(由RAM的可讀可寫屬性決定);RAM裏內存單元的可讀可寫屬性不會由於對工做指針的限定而變化(見下面的第4點),而全部對指針的各類const限定說白了只是對該指針 的 讀寫權限 (包括讀寫位置)進行了限定 。
(1)char *p:p是一個工做指針,能夠用來對任意位置 (非系統敏感區域)進 行讀操做和寫操做 ,一次讀寫一個字節(char佔一個字節)。
(2)const char*p或者char const *p(由於沒有const*p運算,所以const修飾的仍是前面的char):能夠對任意位置(非系統敏感區域)進行「只讀」 操做。(「只讀」是相對於char *p來講所限定的內容)
(3)char *const p(const 修飾的是p):只能對「某個固定的位置」 進 行讀寫操做,而且在定義p時就必須初始化(由於在後面不能執行「p=..」的操做,所以就不能在後面初始化,所以只能在定義時初始化)。(「某個固定的位 置」是相對於char *p來講所限定的內容)
能夠總結以上3點爲:char *p中的指針p一般是」萬能」的工做指針 ,而(2)和(3)只是在(1)的基礎上加了些特定的限制 ,這些限制在程序中並非必須的,只是爲了防止程序員的粗枝大葉而產生事與願違的錯 誤。
另外,要明白「每塊內存空間均可有名字;每塊內存空間內容皆可變(除非有所限) 」 。好比函數裏定義的char s[]="hello";事實上在進程的棧內存裏開闢了6個變量共6個字節的空間,其中6個字符變量的名字分別爲:s1[0]、s1[1]、 s1[2]、s1[3]、s1[4]、s1[5](內容是'\0')
{
待驗證 : 還有一個4字節的指針變量s 。不過s是「有所限制」的,屬於char *const類型,也就是前面說的 (3)這種狀況,s一直指向s[0], 即(*s==s[0]=='h'),能夠經過*s='k'來改變s所指向的 s[0]的值,但不能執行(char *h=「aaa」;s=h;)來對s另外賦值。
}
(4)上面的(2)和(3)只是對p進行限定,沒有也不能對p所指向的空間進行限定,對於"char s[]="hello";const char*p=s;" 雖然不能經過*(p+i)='x'或者p[i]='x'來修改數組元素s[0]~s[4]的值,但能夠經過*(s+i)='x'或者s[i]='x'來修改原數組元素的值--RAM裏內存單元的可讀可寫屬性不因對工做指針的限定而改變,而只會因對其自己的限定而改變。如const char c=‘A’,c是RAM裏一個內存單元(8字節)的名字,該內存單元的內容只可讀,不可寫。
(5)const char **p或者char const**p :涉及兩個指針p和 *p。因爲const修飾char ,對指針p沒有任何限定,對指針*p進行了上面狀況(2)的限定。
(6)char *const *p:涉及兩個指針p和 *p。因爲const修飾前面的char*,也就是對p所指向的內容*p進行了限定(也屬於前面的狀況(2))。而對*p來講,因爲不能經過"*p=..."來進行另外賦值,所以屬於前面的狀況(3)的限定。
(7)char **const p : 涉及兩個指針p和 *p,const修飾p,對p進行上面狀況(3)的限定,而對*p,沒有任何限定。
4、關於char **p 、const char **p的類型相容性問題
1。問題
char *p1;const *p2=p1;//合法
char **p1;const char**p2=p1;//不合法,會有警告warning: initialization from incompatible pointer type
char **p1;char const**p2=p1;//不合法,會有警告warning: initialization from incompatible pointer type
char**p1;char*const*p2=p1;//合法
2。判斷規則
明確const修飾的對象!對於指針p1,和p2,若要使得p2=p1成立,則可讀作 :
「p1是指向X類型的指針,p2是指向「帶有const限定」的X類型的指針 「。
char *p1;const *p2=p1;//合法:p1是指向(char)類型的指針,p2是指向「帶有const限定"的(char)類型的指針。
char **p1;const char**p2=p1;//不合法:p1是指向(char*)類型的指針,p2是指向 ((const char)*)類型的指針。
char **p1;char const**p2=p1;//不合法;與上等價。
char**p1;char*const*p2=p1;//合法: p1是指向(char *)類型的指針,p2是指向「帶有const限定"的(char*)類型的指針。
5、其餘
1。 含有const的單層或雙層指針的統一讀法:
「p是一個指針,是一個[「帶有const限定」的]指向[」帶有const限定」的]X類型的指針」。
2。定義時const修飾的對象是肯定的,但不能在定義時加括號,否則就和定義時用「()」表示的函數類型相混淆了!所以定義時不能寫(char *)const *p或者(const char) **p。
6、問題探討(因爲博文後的留言有字符數目限制,將回復移到這裏)
問題1 (見博文後留言):講解很是好,不過有個問題想探討下: 例如: const char wang[]={"wang"}; char *p; p=wang; 是錯誤的。 因此char *p中的P不能指向常變量。 (1)須要補充糾正。
回覆 : 你好!謝謝指正!我在ubuntu 10.04(gcc 4.4.3)下作了以下測試:
//test_const.c
#include<stdio.h>
int main()
{
const char wang[]={"wang"};
char *p;
p=wang;
p[2]='c';
printf("p is %s\n",p);
return 0;
}
編譯 :
gcc -o test_const test_const.c
輸出以下 :
test_const.c: In function ‘main’:
test_const.c:17: warning: assignment discards qualifiers from pointer target type
執行:
./test_const
p is wacg
結論: 根據本博文中第四點--相容性問題,將const型的wang賦值給p是不合法的。但編譯器對其的處理只是警告,所以執行時經過p修改了只讀區域的數據。這應該是該編譯器處理不嚴所致後果,不知你用的什麼編譯器?
問題2 回答 http://www.linuxsir.org/bbs/showthread.php?t=239058 提到的問題
在c語言裏
// test.c
int main() {
const char* s1 = "test";
char *s2 = s1;
s2 = "It's modified!";
printf("%s\n",s1);
}
out: It's modified!;
這樣也能夠嗎? 照個人理解豈不是const限定符在c語言裏只是擺設一個?
回覆:
(1)首先,以上代碼編譯時會出錯warning: initialization discards qualifiers from pointer target type,
由於char *s2 = s1和問題1提到的同樣,不符合相容規則。
(2)輸出結果是正確的,代碼分析以下:
int main() {
const char* s1 = "test"; // 在只讀數據區(objdump -h test後的.rodata區)開闢5字節存儲"test",並用s1指向首字符‘t’。
char *s2 = s1; // s2也指向只讀數據區中「test」的首字符't'。
s2 = "It's modified!"; // 在只讀數據區開闢15字節存儲"It's modified!",並將s2由指向't'轉而指向"It's modified!"的首字符'I'。
printf("%s\n",s1); // 從s1所指的‘t’開始輸出字符串"test"。
}
(3)總結:提問者的誤區在於,誤覺得s2 = "It's modified!"是對「test」所在區域的從新賦值,其實這裏只是將「萬能」工做指針s2指向另一個新開闢的區域而已。好比若在char *s2 = s1後再執行s2[2]='a'則是對「test」的區域進行了寫操做,執行時會出現段錯誤。但這個段錯誤其實與const沒有關係,由於「test」這塊區域自己就是隻讀的。爲了防止理解出錯,凡事對於對指針的賦值(好比 s2 = "It's modified!" ),則將其讀作:將s2指向「 It's modified! 」所在區域的首字符。
(4)額外收穫:執行gcc -o test test.c後,「test」、「It's modified!」、"%s\n"都被做爲字符串常量存儲在二進制文件test的只讀區
域 (.rodata)。事實上,一個程序從編譯到運行,對變量空間分配的狀況以下:
A。賦值了的全局變量或static變量=>放在可執行文件的.data段。
B。未賦值的全局變量或static變量=>放在可執行文件的.bss段。
C。代碼中出現的字符串常量或加了const的A=>放在可執行文件的.rodata段。
D。通常的局部變量=>在可執行文件中不佔空間,在該二進制文件做爲進程在內存中運行時才爲它分配棧空間。
E。代碼中malloc或new出的變量=>在可執行文件中不佔空間,在該二進制文件做爲進程在內存中運行時才爲它分配堆空間。
問題3:(待進一步分析) 驗證博文中 三(3)提到的是否爲s分配空間,初步分析結果爲:不分配!文中的s只是s[0]的地址的代號而已。
#include<stdio.h>
int main() {
int a=3;
char s1[] = "test";
int b=4;
char s2[] ="test2";
printf("the address of a is %u\n",&a);
printf("s1 is %u\n",s1);
printf("the address of s1 is %u\n",&s1);
printf("the address of b is %u\n",&b);
printf("s2 is %u\n",s2);
printf("the address of s2 is %u\n",&s2);
}
輸出結果:
the address of a is 3213037836
s1 is 3213037827
the address of s1 is 3213037827
the address of b is 3213037832
s2 is 3213037821
the address of s2 is 3213037821
由結果能夠看出,編譯器作了些優化。
7、其餘相關經典文章轉載
王海寧,華清遠見嵌入式學院講師,對const關鍵字的理解
http://***Column/Column311.htm
目前在進行C語言補習時,發現不少的同窗對於const這個關鍵字的理解存在很大的誤解。如今總結下對這個關鍵字理解上的誤區,但願在之後的編程中,可以靈活使用const這個關鍵字。
一、 const修飾的變量是常量仍是變量
對於這個問題,不少同窗認爲const修飾的變量是不能改變,結果就誤認爲該變量變成了常量。那麼對於const修飾的變量該如何理解那?
下面咱們來看一個例子:
int main
{
char buf[4];
const int a = 0;
a = 10;
}
這個比較容易理解,編譯器直接報錯,緣由在於「a = 10;」這句話,對const修飾的變量,後面進行賦值操做。這好像說明了const修飾的變量是不能被修改的,那到底是不是那,那麼下面咱們把這個例子修改下:
int main
{
char buf[4];
const int a = 0;
buf[4] = 97;
printf(「the a is %d\n」,a);
}
其中最後一句printf的目的是看下變量a的值是否改變,根據const的理解,若是const修飾的是變量是不能被修改的話,那麼a的值必定不會改變,確定仍是0。可是在實際運行的結果中,咱們發現a的值已經變爲97了。這說明const修飾的變量a,已經被咱們程序修改了。
那綜合這兩個例子,咱們來分析下,對於第二例子,修改的緣由是buf[4]的賦值操做,咱們知道buf[4]這個變量已經形成了buf這個數組變量的越界訪問。buf數組的成員自己只有0,1,2,3,那麼buf[4]訪問的是誰那,根據局部變量的地址分配,能夠知道buf[4]的地址和int a的地址是同樣,那麼buf[4]實際上就是訪問了const int a;那麼對buf[4]的修改,天然也修改了const int a的空間,這也是爲何咱們在最後打印a的值的時候看到了97這個結果。
那麼咱們如今能夠知道了,const修飾的變量是不具有不容許修改的特性的,那麼對於第一個例子的現象咱們又如何解釋那。
第一個例子,錯誤是在程序編譯的時候給出的,注意這裏,這個時候並無生成可執行文件,說明const修飾的變量能否修改是由編譯器來幫咱們保護了。而第二個例子裏,變量的修改是在可執行程序執行的時候修改的,說明a仍是一個變量。
綜上所述,咱們能夠得出一個結論,那就是const修飾的變量,其實質是告訴程序員或編譯器該變量爲只讀,若是程序員在程序中顯示的修改一個只讀變量,編譯器會絕不留情的給出一個error。而對於因爲像數組溢出,隱式修改等程序不規範書寫形成的運行過程當中的修改,編譯器是無能爲力的,也說明const修飾的變量仍然是具有變量屬性的。
二、 被const修飾的變量,會被操做系統保護,防止修改
若是對於第一個問題,有了理解的話,那麼這個問題,就很是容易知道答案了。Const修飾的變量是不會被操做系統保護的。
其緣由是操做系統只保護常量,而不會保護變量的讀寫。那麼什麼是常量?好比「hello world」這個字符串就是被稱爲字符串常量。
對於這個問題的另外一種證實方法,能夠看下面這個程序:
int main
{
const int a;
char *buf = 「hello world」;
printf(「the &a is %p, the buf is %p\n」,&a, buf);
}
能夠發現buf保存的地址是在0x08048000這個地址附近的,而a的地址是在0xbf000000這個地址附近的,而0x08048000附近的地址在咱們linux操做系統上是代碼段。這也說明了常量和變量是存放在不一樣區域的,天然操做系統是會保護常量的。
若是咱們知道這個道理後,再看下面的題目:
int main
{
char *buf = 「hello」;
buf[0] = ‘a’;
printf(「the buf is %s\n」,buf);
}
咱們能夠思考下,這個程序的運行結果會是什麼呢?