1. 程式人生 > >深入理解const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p

深入理解const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p

一、可能的組合:

     (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中,其他的就可類推了!

二、理解助記法寶:

     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,因為在定義是"()"是表示函式。

三、深入理解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')

{

   待驗證 (見後面問題3的分析) : 還有一個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,沒有任何限定。

四、關於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*)型別的指標。

五、其他

    1。 含有const的單層或雙層指標的統一讀法:

         “p是一個指標,是一個[“帶有const限定”的]指向[”帶有const限定”的]X型別的指標”。

    2。定義時const修飾的物件是確定的,但不能在定義時加括號,不然就和定義時用“()”表示的函式型別相混淆了!因此定義時不能寫(char *)const *p或者(const char) **p。

六、問題探討(由於博文後的留言有字元數目限制,將回復移到這裡)

  問題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修改了只讀區域的資料。這應該是該編譯器處理不嚴所致後果,不知你用的什麼編譯器?

在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

    由結果可以看出,編譯器做了些優化。

 七、其他相關經典文章轉載

  王海寧, 華清遠見嵌入式學院 講師,對const關鍵字的理解

     目前在進行C語言補習時,發現很多的同學對於const這個關鍵字的理解存在很大的誤解。現在總結下對這個關鍵字理解上的誤區,希望在以後的程式設計中,能夠靈活使用const這個關鍵字。

1、 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修飾的變數仍然是具備變數屬性的。

2、 被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作業系統上是程式碼段。這也說明了常量和變數是存放在不同區域的,自然作業系統是會保護常量的。

如果我們知道這個道理後,再看下面的題目:

#include<stdio.h>
int main()
{
 char *buf = "hello";
 buf[0] = 'a';
 printf("the buf is %s/n",buf);
}

我們可以思考下,這個程式的執行結果會是什麼呢?