天天看點

char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on

本文測試環境 :  X86-64 bit 架構的伺服器 CentOS x64 5.x gcc version 4.1.2 20080704

指針和數組是C的比較難搞懂的知識點, 需要結合記憶體來學習, 非常感謝各位兄弟為我指點迷津. 下面總結一下 :  首先說明一下C程式在運作時, 不同的内容或變量分别存儲在什麼地方? 分了幾塊區域分别是, code, constants, global, heap, stack; (記憶體位址從低到高) 其中constants存儲常量(常量值不允許修改), global存儲在所有函數以外定義的全局變量(全局變量允許修改), heap是一塊動态記憶體區域(可存放持久化内容, 不會自動釋放記憶體), stack存放函數内的本地變量(函數執行完後本地變量占用的記憶體将自動釋放);  

如圖 : 

char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on

接下來要介紹兩個符号 * 和 &. 1. * 用在定義變量類型, 或者 強制類型轉換時, 表示要定義一個指針 或者 把這個變量類型轉換成指針(并告知這個指針指向的是什麼類型的資料) . 2. * 用在一個指針變量的前面, 表示這個指針指向位址存儲的内容 (内容是什麼類型的就取決于這個指針定義時:  告知這個指針指向的是什麼類型的資料,指針的加減運算得到的記憶體位址也和指針 指向的是什麼類型的資料相關,int * a,a+1 得到的位址則是在a指向的位址基礎上加4位元組.). 3. & 用在變量名的前面, 表示這個變量所在的位址 . 

例1 : 

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char * a = "hello";
   
   
      fprintf(stdout, "&a:%p, a:%p, a:%s\n", &a, a, a);
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    &a:0x7fff71b22230, a:0x400618, a:hello
   

      

&a 表示a的位址 .  a 表示a存儲的内容, 因為a是指針, 是以它的内容讀取出來就是一個位址 .  *a 表示a指針存儲的這個位址 存儲的内容 .  在記憶體中的圖示 : 

char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on

  這個圖包含了幾塊内容 :  1. 在64位的系統中 , 指針占用了8個位元組. 因為指針中存儲的是位址, 而且位址是64位的. 是以需要8個位元組. 2. 在x86架構的機器中, 記憶體填充是從低位到高位的. 是以hello在記憶體中是這樣存儲的 :  位址:  内容

0x400618 : 0x68  (ascii : h)
   
   
    0x400619 : 0x65  (ascii : e) 
   
   
    0x40061a : 0x6c  (ascii : l) 
   
   
    0x40061b : 0x6c  (ascii : l) 
   
   
    0x40061c : 0x6f   (ascii : o) 
   
   
    0x40061d : 0x68  (ascii : NULL)
   

      

3. 那怎麼來證明以上是正确的呢? 很簡單, 按照每個位元組來列印就知道了. 這裡需要注意的是指針的加減法得到的位址和指針指向的位址存儲的内容的類型是有關的, 反過來說, 要讓指針加1剛好得到的是下一個位元組那就告訴編譯器, 這個指針指向的位址的内容是char類型就好了, 因為sizeof(char) = 1位元組. 先來列印一下hello是不是按照上面說的這樣存儲的?

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char * a = "hello";
   
   
      fprintf(stdout, "&a:%p, a:%p, a:%s\n", &a, a, a);
   
   
      fprintf(stdout, "sizeof(char *):%lu, sizeof(char):%lu\n", sizeof(char *), sizeof(char));
   
   
      fprintf(stdout, "a+0:%p, *(a+0):%x, *(a+0):%c\n", a+0, *(a+0), *(a+0));
   
   
      fprintf(stdout, "a+1:%p, *(a+1):%x, *(a+1):%c\n", a+1, *(a+1), *(a+1));
   
   
      fprintf(stdout, "a+2:%p, *(a+2):%x, *(a+2):%c\n", a+2, *(a+2), *(a+2));
   
   
      fprintf(stdout, "a+3:%p, *(a+3):%x, *(a+3):%c\n", a+3, *(a+3), *(a+3));
   
   
      fprintf(stdout, "a+4:%p, *(a+4):%x, *(a+4):%c\n", a+4, *(a+4), *(a+4));
   
   
      fprintf(stdout, "a+5:%p, *(a+5):%x, *(a+5):%c\n", a+5, *(a+5), *(a+5));
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    &a:0x7ffffe249680, a:0x4006f8, a:hello
   
   
    sizeof(char *):8, sizeof(char):1
   
   
    a+0:0x4006f8, *(a+0):68, *(a+0):h
   
   
    a+1:0x4006f9, *(a+1):65, *(a+1):e
   
   
    a+2:0x4006fa, *(a+2):6c, *(a+2):l
   
   
    a+3:0x4006fb, *(a+3):6c, *(a+3):l
   
   
    a+4:0x4006fc, *(a+4):6f, *(a+4):o
   
   
    a+5:0x4006fd, *(a+5):0, *(a+5):
   

      

解說 :    a 是一個指針, 這個指針指向的内容是char類型的, 是以這個指針的加減運算a+1 表示位址加1位元組.   *a 把你帶到它指向的 0x4006f8, 而fprintf 以16進制和char輸出的正是 0x4006f8這個位址的 這個位元組上的内容(1位元組剛好用兩個16進制數表示, 剛好可以轉成char)   a+0這裡隻是為了說明指針的加減運算, 可以去掉+0.    sizeof(char *):8 表示指針占用了8位元組, sizeof(char):1 表示char占用了1位元組.

4. 接下來列印 &a 這個位址是不是也是按照從低到高存儲的? 為了一個位元組一個位元組打出,  我們需要再定義一個指針b , 用指針b來做加減運算, 但是這樣能如願嗎?

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char * a = "hello";
   
   
      char ** b = &a;
   
   
      fprintf(stdout, "sizeof(a):%lu, b:%p, &a:%p, b+1:%p\n", sizeof(a), b, &a, b+1);
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    sizeof(a):8, b:0x7fff3692b520, &a:0x7fff3692b520, b+1:0x7fff3692b528
   

      

解說 :  sizeof(a)=8 結果告訴我們, a這個變量占用了8位元組, 因為它存儲的是個記憶體位址, 在64位的作業系統中這個是可以了解的. b這個指針指向a, 是以b和&a 列印出來的值轉成記憶體位址當然是一樣的. b+1 運算得到的位址當然應該是加8個位元組 . 因為位址做加減運算得到的當然還是位址, 至于得到的結果和什麼有關, 當然和這個做加減運算的指針定義時所告知的它指向什麼類型的資料有關. char ** b, ( *b 表示 b是一個指針, 然後char *則是告訴你b指向的是一個指針, 甭管是什麼類型的指針,反正b指向的是一個指針, 一個指針占用8位元組, 是以b+1就是加8個位元組)

那怎麼樣能讓b+1結果是加1個位元組呢? 這裡要用到強制類型轉換. (char *) b 就把b強制轉成char *了.  是以 ((char *) b)+1 就是加1位元組. *( ((char *) b)+1) 就是取這個位址的内容. %x表示取一個位元組并按照16進制列印出來.

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char * a = "hello";
   
   
      char ** b = &a;
   
   
      unsigned short i;
   
   
      unsigned short x = (unsigned short) sizeof(a);
   
   
      fprintf(stdout, "&a:%p, a:%p\n", &a, a);
   
   
      for(i=0; i<x; i++) {
   
   
        fprintf(stdout, "i:%u, ((char *) b)+i:%p, *(((char *) b)+i):%x\n", i, ((char *) b)+i, *(((char *) b)+i));
   
   
      }
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    &a:0x7fff1ffff608, a:0x400728
   
   
    i:0, ((char *) b)+i:0x7fff1ffff608, *(((char *) b)+i):28
   
   
    i:1, ((char *) b)+i:0x7fff1ffff609, *(((char *) b)+i):7
   
   
    i:2, ((char *) b)+i:0x7fff1ffff60a, *(((char *) b)+i):40
   
   
    i:3, ((char *) b)+i:0x7fff1ffff60b, *(((char *) b)+i):0
   
   
    i:4, ((char *) b)+i:0x7fff1ffff60c, *(((char *) b)+i):0
   
   
    i:5, ((char *) b)+i:0x7fff1ffff60d, *(((char *) b)+i):0
   
   
    i:6, ((char *) b)+i:0x7fff1ffff60e, *(((char *) b)+i):0
   
   
    i:7, ((char *) b)+i:0x7fff1ffff60f, *(((char *) b)+i):0
   

      

解說 :  a變量存放在記憶體位址 0x7fff1ffff608這裡的連續8個位元組中. a變量的8個位元組中存儲了什麼内容呢?  0x400728 它是從低位到高位存儲的, 如上面的結果, 28存在 0x7fff1ffff608, 07存在 0x7fff1ffff609, 40存在 0x7fff1ffff60a, 另外5個位元組存的都是0x00.

5. 如果是多位元組的資料又是怎麼存儲的呢? 比如int 占據了4個位元組, 它是怎麼存儲的 ? 答案也是從低位到高位.

[[email protected]-172-16-3-33 zzz]# cat c.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      int a = -987654;
   
   
      int * b = &a;
   
   
      unsigned short i;
   
   
      unsigned short x = (unsigned short) sizeof(a);
   
   
      fprintf(stdout, "&a:%p, a:%i, a:%x\n", &a, a, a);
   
   
      for(i=0; i<x; i++) {
   
   
        fprintf(stdout, "i:%u, ((char *) b)+i:%p, (unsigned char) (*(((char *) b)+i)):%x\n", i, ((char *) b)+i, (unsigned char) (*(((char *) b)+i)));
   
   
      }
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
   
   
    &a:0x7fffe57051cc, a:-987654, a:fff0edfa  //負數是正數的反碼加1, 是以得到的是這個結果.
   
   
    i:0, ((char *) b)+i:0x7fffe57051cc, (unsigned char) (*(((char *) b)+i)):fa
   
   
    i:1, ((char *) b)+i:0x7fffe57051cd, (unsigned char) (*(((char *) b)+i)):ed
   
   
    i:2, ((char *) b)+i:0x7fffe57051ce, (unsigned char) (*(((char *) b)+i)):f0
   
   
    i:3, ((char *) b)+i:0x7fffe57051cf, (unsigned char) (*(((char *) b)+i)):ff
   

      

注意, 這裡一定要把内容再強制轉換成unsigned char再輸出, 就是這個 (unsigned char) (*(((char *) b)+i)):%x .  否則編譯器會輸出4位元組的16進制數, 不太好看. 如下 : 

#include <stdio.h>
    
    
     

    
    
     int main() {
    
    
       int a = -987654;
    
    
       int * b = &a;
    
    
       unsigned short i;
    
    
       unsigned short x = (unsigned short) sizeof(a);
    
    
       fprintf(stdout, "&a:%p, a:%i, a:%x\n", &a, a, a);
    
    
       for(i=0; i<x; i++) {
    
    
         fprintf(stdout, "i:%u, ((char *) b)+i:%p, (*(((char *) b)+i)):%x\n", i, ((char *) b)+i, (*(((char *) b)+i)));
    
    
       }
    
    
       return 0;
    
    
     }
    
   
   
    結果 : 
   
   
    
    
     [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
    
    
     &a:0x7ffff6fada3c, a:-987654, a:fff0edfa
    
    
     i:0, ((char *) b)+i:0x7ffff6fada3c, (*(((char *) b)+i)):fffffffa
    
    
     i:1, ((char *) b)+i:0x7ffff6fada3d, (*(((char *) b)+i)):ffffffed
    
    
     i:2, ((char *) b)+i:0x7ffff6fada3e, (*(((char *) b)+i)):fffffff0
    
    
     i:3, ((char *) b)+i:0x7ffff6fada3f, (*(((char *) b)+i)):ffffffff
    
   

      

6. 那麼bit-field又是怎麼存儲的呢? 答案當然也是從低位到高位存儲的, 這裡要用到struct來驗證.

[[email protected]-172-16-3-33 zzz]# cat c.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      typedef struct test{
   
   
        unsigned char f1:1;
   
   
        unsigned char f2:2;
   
   
        unsigned char f3:3;
   
   
        unsigned char f4:2;
   
   
      } test;
   
   
      test t1 = {0,1,4,3};
   
   
      test * pt1 = &t1;
   
   
      fprintf(stdout, "(unsigned char) (*((unsigned char *) pt1)):%x\n", (unsigned char) (*((unsigned char *) pt1)));
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
   
   
    (unsigned char) (*((unsigned char *) pt1)):e2
   

      

解說 : 

test t1 = {0,1,4,3}; 轉成二進制分别如下 : 
   
   
    f1(0) : 0
   
   
    f2(1) : 01
   
   
    f3(4) : 100
   
   
    f4(3) : 11
   

      

如果是按低位到高位存儲的, 那麼它存儲的應該是 : 11100010 . 這個與0xe2 剛好相符. 這裡同樣用到了強制類型轉換,  (unsigned char *) pt1是把指向struct test的指針轉成了指向unsigned char的指針. (unsigned char) (*((unsigned char *) pt1)) 是把  *((unsigned char *) pt1) 轉成了unsigned char類型.  如果不使用指針, 直接把struct變量轉成unsigned是不能編譯通過的. 錯誤如下 : 

[[email protected]-172-16-3-33 zzz]# cat c.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      typedef struct test{
   
   
        unsigned char f1:1;
   
   
        unsigned char f2:2;
   
   
        unsigned char f3:3;
   
   
        unsigned char f4:2;
   
   
      } test;
   
   
      test t1 = {0,1,4,3};
   
   
      unsigned char a = (unsigned char) t1;
   
   
      fprintf(stdout, "a:%x\n", a);
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
   
   
    ./c.c: In function ‘main’:
   
   
    ./c.c:11: error: aggregate value used where an integer was expected
   

      

是以指針在C裡面運用真的太靈活了.

進入正題, 講講 char **a , char *a[] , char a[][], char * a[][] , char ** a[][] , char * a [][][] 看起來很複雜, 其實了解了就不複雜了. 1.  char **a :  表示a是一個指針, 這個指針指向的位址存儲的是 char * 類型的資料.  指針的加減運算在這裡的展現 :  a + 1 表示位址加8位元組 .   char * 也是一個指針,  用(*a)表示 , 指向的位址存儲的是 char 類型的資料。 指針的加減運算在這裡的展現 : (* a) + 1 表示位址加1位元組 .  

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char * a = "hello";
   
   
      char ** b = &a;
   
   
      fprintf(stdout, "&b:%p, b:%p, &a:%p, a:%p, *a:%c, a:%s\n", &b, b, &a, a, *a, a);
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    &b:0x7fff5319c1d8, b:0x7fff5319c1e0, &a:0x7fff5319c1e0, a:0x400628, *a:h, a:hello
   

      

圖示 : 

char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on
char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on

2.  char *a[] 表示 a是數組, 數組中的元素是指針, 指向char類型. (數組裡面所有的元素是連續的記憶體存放的). 需要特别注意 :  數組名在C裡面做了特殊處理 , 數組名用數組所占用記憶體區域的第一個位元組的記憶體位址替代了。并且數組名a也表示指針. 如數組占用的記憶體區域是0x7fff5da3f550到0x7fff5da3f5a0, 那麼a就被替換成 0x7fff5da3f550. 是以a 并不表示a位址存儲的内容, 而是a位址本身(這個從 a = &a 就能夠展現出來). 這個一定要了解, 否則會無法進行下去.  a+1 表示a的第二個元素的記憶體位址, 是以是加8位元組.( 因為a的元素是char 指針, 所需要的空間為8位元組(64位記憶體位址). ) *(a+1) 則表示a這個數組的第二個元素的内容 (是個char 類型的指針. 本例表示為world字元串的位址). *(*(a+1)) 則表示 a這個數組的第二個元素的内容(char指針)所指向的内容( w字元). char * a[10] 表示限定這個數組最多可存放10個元素(char指針), 也就是說這個數組占用10*8 = 80位元組. 如果存儲超出數組的限額編譯警告如下 : 

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char *a[1] = {"abc","def"};
   
   
      fprintf(stdout, "a[1]:%s\n", a[1]);
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    cc1: warnings being treated as errors
   
   
    ./b.c: In function ‘main’:
   
   
    ./b.c:4: warning: excess elements in array initializer  // 超出數組長度. 因為指派時給了2個元素, 而限定隻有1個元素.
   
   
    ./b.c:4: warning: (near initialization for ‘a’)
   

      

例子 : 

char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on
[[email protected]-172-16-3-33 zzz]# cat b.c
    
    
     #include <stdio.h>
    
    
     

    
    
     int main() {
    
    
       char *a[10] = {"hello", "world"};
    
    
       fprintf(stdout, "a:%p, a+1:%p, &a:%p, (&a)+1:%p, *a:%p, *(a+1):%p\n", a, a+1, &a, (&a)+1, *a, *(a+1));
    
    
       fprintf(stdout, "*a:%s, *(a+1):%s, **a:%c, *(*(a+1)):%c\n", *a, *(a+1), **a, *(*(a+1)));
    
    
       return 0;
    
    
     }
    
   
   
    結果 : 
   
   
    
     [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
    
    
     a:0x7fff9d0fb180, a+1:0x7fff9d0fb188, &a:0x7fff9d0fb180, (&a)+1:0x7fff9d0fb1d0, *a:0x4006b8, *(a+1):0x4006be
    
    
     *a:hello, *(a+1):world, **a:h, *(*(a+1)):w
    
   

      

解說 :  a:%p 列印  0x7fff9d0fb180  表示a的記憶體位址 , 也就是說數組a的第一個元素存儲在這個位址裡面, 取出第一個元素的值(char指針)使用*a  . a+1:%p 列印的是a這個數組的第二個元素 的記憶體位址   0x7fff5da3f558 (因為a數組裡面存儲的是char指針, 指針在64位機器裡面占用8位元組, 是以第二個元素所在的記憶體位址相比第一個元素所在的記憶體位址剛好大8位元組) , 取出第二個元素的值(char指針)使用*(a+1)  . &a:%p 列印出來的結果和a:%p 0x7fff5da3f550 一緻, 因為編譯器把數組名替換成了數組所在記憶體的位置 .  (a:%p 列印的是第一個元素所在的記憶體位址, &a:%p列印的則是a數組所在的記憶體位址. 是以結果是一樣的) 但是 a+1 不等于 (&a)+1. 原因是 a 和 &a 含義不一樣, a指向的是數組裡面的第一個元素, 是以a+1 指的是第二個元素. &a指向的是a這個數組, 是以(&a)+1 表示整個a數組要用到的記憶體空間的下一個位址. ( 如果&a不好了解的話, 想象把&a 賦予給另一個指針變量c, 那麼c+1 等于啥? )

3. char [][] char a[2][10] 表示一個二維數組, 存儲在最底層的元素是char. 1維最多允許存儲2個元素(char []), 2維最多允許存儲10個元素(char). 超出将告警 : 

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char a[2][10] = {"hello", "world", "linux"};
   
   
      return 0;
   
   
    }
   
   
    結果 : 
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    cc1: warnings being treated as errors
   
   
    ./b.c: In function ‘main’:
   
   
    ./b.c:4: warning: excess elements in array initializer  // 1維在指派時給出了3個元素"hello" , "world" , "linux" 是以告警了, 可能導緻記憶體溢出.
   
   
    ./b.c:4: warning: (near initialization for ‘a’)
   
   
    ./b.c:4: warning: unused variable ‘a’
   

      

2維超出長度同樣告警 : 

[[email protected]-172-16-3-33 zzz]# cat b.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char a[2][10] = {"helloxxxxxxxx", "world"};
   
   
      return 0;
   
   
    }
   
   
    [[email protected]-172-16-3-33 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./b.c -o b && ./b
   
   
    cc1: warnings being treated as errors
   
   
    ./b.c: In function ‘main’:
   
   
    ./b.c:4: warning: initializer-string for array of chars is too long
   
   
    ./b.c:4: warning: (near initialization for ‘a[0]’)
   
   
    ./b.c:4: warning: unused variable ‘a’
   

      

char a[2][10] 中, a+$n 或者 & a[n] 表示 指向1維的第$n個元素的指針, *(a+$n)+$m 或 & a[n][m] 表示指向1維的第n個元素中的第m個元素的指針.  *( *(a+$n)+$m) 或者 a[n][m] 表示  1維的第n個元素中的第m個元素 的内容 . 

char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on

  解說 :  以 char a[2][10]為例子 :  &a 是一個指針 , 加1 将加整個二維數組所占空間. 相當于加20位元組. a 是一個指針 , 加1 将加加1個1維元素所占空間.  相當于加10位元組. *a 是一個指針    ,  加1 将加加1個2維元素所占空間.  相當于加1位元組. **a 不是指針, 是char.  a+1與*(a+1)相等 是因為a+1 表示1維的第2個元素的首位址, *(a+1)表示 1維的第2個元素中的第1個元素的位址. 指向同一個位址. 但是含義不一樣.  (a+1) + 1 和 (*(a+1)) + 1就不一樣了. (a+1) + 1  表示1維的第3個元素的首位址;  (*(a+1)) + 1  表示 1維的第2個元素中的第2個元素的位址. 

以char a[2][6]為例子 : 

[[email protected]-172-16-3-150 zzz]# cat a.c
     
     
      #include <stdio.h>
     
     
      

     
     
      int main() {
     
     
        char a[2][6] = {"hello", "world"};
     
     
        fprintf(stdout, "sizeof(a):%lu\n", sizeof(a)); // a數組占用空間
     
     
        fprintf(stdout, "&a:%p, (&a)+1:%p\n", &a, (&a)+1); // a數組首位址, a數組首位址加1
     
     
        fprintf(stdout, "a:%p, a+1:%p\n", a, a+1); // a數組1次元第1個元素首位址, 加1
     
     
        fprintf(stdout, "*a:%p, *(a)+1:%p\n", *a, *(a)+1); // a數組1次元第1個元素中的第1個元素首位址, 加1
     
     
        return 0;
     
     
      }
     
    
    
     結果 : 
    
    
     
      [[email protected] zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
     
     
      sizeof(a):12
     
     
      &a:0x7ffffe5cf8b0, (&a)+1:0x7ffffe5cf8bc  // 加12個位元組
     
     
      a:0x7ffffe5cf8b0, a+1:0x7ffffe5cf8b6  // 加6個位元組
     
     
      *a:0x7ffffe5cf8b0, *(a)+1:0x7ffffe5cf8b1    // 加1個位元組
     
    

      

驗證圖示正确性 : 

[[email protected]-172-16-3-150 zzz]# cat a.c
    
    
     #include <stdio.h>
    
    
     

    
    
     int main() {
    
    
       char a[2][6] = {"hello", "world"};
    
    
       fprintf(stdout, "&a:%p, a:%p, *a:%p\n", &a, a, *a);
    
    
       fprintf(stdout, "(*(a+0))+1:%p, &a[0][1]:%p\n", (*(a+0))+1, &a[0][1]);
    
    
       fprintf(stdout, "a+1:%p, &a[1]:%p, *(a+1):%p, a[1]:%p\n", a+1, &a[1], *(a+1), a[1]);
    
    
       fprintf(stdout, "(*(a+1))+1:%p, &a[1][1]:%p\n", (*(a+1))+1, &a[1][1]);
    
    
       fprintf(stdout, "(&a)+1:%p\n", (&a)+1);
    
    
       return 0;
    
    
     }
    
    
     結果 : 
    
    
     [[email protected]-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
    
    
     &a:0x7fff5c94e670, a:0x7fff5c94e670, *a:0x7fff5c94e670
    
    
     (*(a+0))+1:0x7fff5c94e671, &a[0][1]:0x7fff5c94e671
    
    
     a+1:0x7fff5c94e676, &a[1]:0x7fff5c94e676, *(a+1):0x7fff5c94e676, a[1]:0x7fff5c94e676
    
    
     (*(a+1))+1:0x7fff5c94e677, &a[1][1]:0x7fff5c94e677
    (&a)+1:0x7fff5c94e67c 

      

指針運用 : fprintf 列印 %s 需要傳入char *指針 . %c 需要傳入的是值.  下面試試使用 a+1 和 *(a+1) 來列印%s, a+1 會報錯, 因為它是一個指向指針的指針. 不是char *. 

[[email protected]-172-16-3-150 zzz]# cat a.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char a[2][6] = {"hello", "world"};
   
   
      fprintf(stdout, "a+1:%s\n", a+1);
   
   
      return 0;
   
   
    }
   
   
    [[email protected]-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
   
   
    cc1: warnings being treated as errors
   
   
    ./a.c: In function ‘main’:
   
   
    ./a.c:5: warning: format ‘%s’ expects type ‘char *’, but argument 3 has type ‘char (*)[6]’
   

      

将 a+1 指針強制轉換成char * 就可以了 (char *) (a+1) . 如下 : 

[[email protected]-172-16-3-150 zzz]# cat a.c
   
   
    #include <stdio.h>
   
   
    

   
   
    int main() {
   
   
      char a[2][6] = {"hello", "world"};
   
   
      fprintf(stdout, "a+1:%s\n", (char *) (a+1));
   
   
      return 0;
   
   
    }
   
   
    [[email protected]-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
   
   
    a+1:world
   

      

你可能覺得不對勁,  (char *) (a+1) 到底是強制轉換還是去a+1這個位址的内容? 那我們就用一個三維數組來驗證一下, 如果是取這個位址的值, 那麼得到的應該是一個指向指針的指針. 也是不能列印的. 如下 : 

[[email protected]-172-16-3-150 zzz]# cat a.c
    
    
     #include <stdio.h>
    
    
     

    
    
     int main() {
    
    
       char a[2][6][6] = {{"hello", "world"},{"linux", "digoal"}};
    
    
       fprintf(stdout, "a+1:%s\n", (a+1));
    
    
       return 0;
    
    
     }
    
    
     結果
    
    
     [[email protected]-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
    
    
     cc1: warnings being treated as errors
    
    
     ./a.c: In function ‘main’:
    
    
     ./a.c:5: warning: format ‘%s’ expects type ‘char *’, but argument 3 has type ‘char (*)[6][6]’
    
   
   
    

   
   
    
     [[email protected]-172-16-3-150 zzz]# cat a.c
    
    
     #include <stdio.h>
    
    
     

    
    
     int main() {
    
    
       char a[2][6][6] = {{"hello", "world"},{"linux", "digoal"}};
    
    
       fprintf(stdout, "a+1:%s\n", *(a+1));
    
    
       return 0;
    
    
     }
    
    
     結果
    
    
     [[email protected]-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
    
    
     cc1: warnings being treated as errors
    
    
     ./a.c: In function ‘main’:
    
    
     ./a.c:5: warning: format ‘%s’ expects type ‘char *’, but argument 3 has type ‘char (*)[6]’
    
   
   
    

   
   
    
     [[email protected]-172-16-3-150 zzz]# cat a.c
    
    
     #include <stdio.h>
    
    
     

    
    
     int main() {
    
    
       char a[2][6][6] = {{"hello", "world"},{"linux", "digoal"}};
    
    
       fprintf(stdout, "a+1:%s\n", (char *) (a+1));
    
    
       return 0;
    
    
     }
    
    
     結果
    
    
     [[email protected]-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
    
    
     a+1:linux
    
   

      

從上面得到一個結論, 隻要是數組名, 就會被替換成位址, 不管是1維還是2維. 想象一下a[2][6], a是1維的數組名, *a是2維的數組名. 都是替換成位址了. 多元也是如此.

【其他】 1. 當char []作為函數的參數時, 表示 char *. 當作為函數的參數傳入時, 實際上是拷貝了數組的第一個元素的位址 .      是以 void test (char a[]) 等同于 void test ( char * a )     char x[10] ; 然後調用 test(x) 則等同于把 x 的第一個元素的位址賦予給參數 a .  2. char * a 和 char a[]  相同點 : a都是指針,  指向char類型. 不同點 : char a[] 把内容存在stack .                char *a 則把指針存在stack,把内容存在constants.  3. char * a[10] 和 char a[10][20] 相同點 : a 都是2級指針, *a 表示一級指針, **a 表示記憶體中存儲的内容. 不同點 :  char * a[10], 數組由char * 類型的指針組成;                 char a [10][20] 表示一位放10個元素, 二維放20個元素, 值存放地是一塊連續的記憶體區域, 沒有指針.

4. 小竅門 :  []和*的數量對應, 如 char a[][]的指針層數是2, 相當于char **a; char *a[]也是如此, 兩層指針. 迷糊的時候數數到底有幾個*幾個[], 就知道什麼情況下存儲的是内容還是位址了? 如char a[][] 的情況裡面: &a, a, *a 都是位址, **a 是内容.

【參考】

SYNOPSIS
    
    
            #include <stdio.h>
    
   
   
           int fprintf(FILE *stream, const char *format, ...);