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

本文测试环境 : 

X86-64 bit 架构的服务器

CentOS x64 5.x

gcc version 4.1.2 20080704

指针和数组是C的比较难搞懂的知识点, 需要结合内存来学习, 非常感谢各位兄弟为我指点迷津.

下面总结一下 : 

首先说明一下C程序在运行时, 不同的内容或变量分别存储在什么地方?

分了几块区域分别是, code, constants, global, heap, stack; (内存地址从低到高)

其中constants存储常量(常量值不允许修改), global存储在所有函数以外定义的全局变量(全局变量允许修改), heap是一块动态内存区域(可存放持久化内容, 不会自动释放内存), stack存放函数内的本地变量(函数执行完后本地变量占用的内存将自动释放);  

如图 : 


接下来要介绍两个符号 * 和 &.

1. * 用在定义变量类型, 或者 强制类型转换时, 表示要定义一个指针 或者 把这个变量类型转换成指针(并告知这个指针指向的是什么类型的数据) .

2. * 用在一个指针变量的前面, 表示这个指针指向地址存储的内容 (内容是什么类型的就取决于这个指针定义时: 告知这个指针指向的是什么类型的数据,指针的加减运算得到的内存地址也和指针指向的是什么类型的数据相关,int * a,a+1 得到的地址则是在a指向的地址基础上加4字节.).

3. & 用在变量名的前面, 表示这个变量所在的地址 . 

例1 : 

[root@db-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;
}
结果 :
[root@db-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指针存储的这个地址 存储的内容 . 

在内存中的图示 : 


 

这个图包含了几块内容 : 

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是不是按照上面说的这样存储的?

[root@db-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;
}
结果 :
[root@db-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来做加减运算, 但是这样能如愿吗?

[root@db-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;
}
结果 :
[root@db-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进制打印出来.

[root@db-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;
}
结果 :
[root@db-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个字节, 它是怎么存储的 ?

答案也是从低位到高位.

[root@db-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;
}
结果 :
[root@db-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;
}
结果 :
[root@db-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来验证.

[root@db-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;
}
结果 :
[root@db-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是不能编译通过的. 错误如下 : 

[root@db-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;
}
结果 :
[root@db-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字节 .  

[root@db-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;
}
结果 :
[root@db-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

图示 : 


  


 

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字节.

如果存储超出数组的限额编译警告如下 : 

[root@db-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;
}
结果 :
[root@db-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’)

例子 : 


 

[root@db-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;
}
结果 :
[root@db-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).

超出将告警 : 

[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>

int main() {
  char a[2][10] = {"hello", "world", "linux"};
  return 0;
}
结果 :
[root@db-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维超出长度同样告警 : 

[root@db-172-16-3-33 zzz]# cat b.c
#include <stdio.h>

int main() {
  char a[2][10] = {"helloxxxxxxxx", "world"};
  return 0;
}
[root@db-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[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]为例子 : 

[root@db-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;
}
结果 :
[root@db-172-16-3-150 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个字节

验证图示正确性 : 

[root@db-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;
}
结果 :
[root@db-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 *. 

[root@db-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;
}
[root@db-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) . 如下 : 

[root@db-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;
}
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./a.c -o a && ./a
a+1:world

你可能觉得不对劲, (char *) (a+1) 到底是强制转换还是去a+1这个地址的内容? 那我们就用一个三维数组来验证一下, 如果是取这个地址的值, 那么得到的应该是一个指向指针的指针. 也是不能打印的. 如下 : 

[root@db-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;
}
结果
[root@db-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]’

[root@db-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;
}
结果
[root@db-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]’

[root@db-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;
}
结果
[root@db-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, ...);
时间: 2024-09-09 12:09:35

char * a, char ** a, char * a[], char a[][], char * a[][], char ** a[][], char * a [][][], and so on in memory的相关文章

c语言-C 中将一个int型数赋值给char型数组的前4字节,数组后面仍为char型

问题描述 C 中将一个int型数赋值给char型数组的前4字节,数组后面仍为char型 C 中将一个int型数赋值给char型数组的前4字节,数组后面仍为char型 解决方案 最简单的是使用联合 union X { int val; char[100]; } X.val=1; http://blog.csdn.net/jiangnanyouzi/article/details/3158702 解决方案二: http://www.jb51.net/article/56009.htmhttp://v

C/C++误区四:char c = getchar();

许多初学者都习惯用 char 型变量接收 getchar.getc,fgetc 等函数的返 回值,其实这么做是不对的,并且隐含着足以致命的错误.getchar 等函数的返 回值类型都是 int 型,当这些函数读取出错或者读完文件后,会返回 EOF.EOF 是一个宏,标准规定它的值必须是一个 int 型的负数常量.通常编译器都会把 EOF 定义为 -1.问题就出在这里,使用 char 型变量接收 getchar 等函数的返 回值会导致对 EOF 的辨认出错,或者错把好的数据误认为是 EOF,或者把

Mysql数据库Char和Varchar字段类型长度的选择比较

  网上有很多关于char和varchar的相关比较,但是都历史悠久,这里转载一篇信息比较新的,个人认为对我的设计字段决定帮助很大. 现代数据库一般都支持CHAR与VARCHAR字符型字段类型,CHAR是用来保存定长字符,存储空间的大小为字段定义的长度,与实际字符长度无关,当输入的字符小于定义长度时最后会补上空格.VARCHAR是用来保留变长字符,在数据库中存储空间的大小是实际的字符长度,不会像CHAR一样补上空格,这样占用的空间更少. 从以上特点来看,VARCHAR比CHAR有明显的优势,因此

C# char类型字符转换大小写的实现代码

以下是对C#中char类型字符转换大小写的示例代码进行了介绍,需要的朋友可以过来参考下哦   C# char类型有自带的大小写转换方法:ToUpper和ToLowerchar str1 = 'a'; char str2 = 'A'; Char.ToUpper(str1); Char.ToLower(str2);

初始化-C++ char数组不能赋值中文

问题描述 C++ char数组不能赋值中文 #includeusing namespace std;class student{public: char *getname() { cout << ""姓名:""; cin >> name[10]; //为什么输入字母.数字都可以正常运行,输入中文就不行 count++; return name; } void seteng() { cout << ""英语:&q

char a = &amp;amp;quot;C&amp;amp;quot;;printf(&amp;amp;quot;%c&amp;amp;quot;,a);打印问题

问题描述 char a = "C";printf("%c",a):打印问题 我用VS2013运行这个代码,老打印 T,将a初始化为"X",X代表其他字符,还是打印 T,这是怎么回事? 解决方案 char a = "C",这里"C"不是字符常量,它表示的是两个字符(字符C和)组成的字符串,"C"实际上表示的是字符串所在的内存地址,上面那条语句试图将一个内存地址赋给a,内存地址占4个字节,而

char数组转换cstring求教

问题描述 char数组转换cstring求教 例如char ch[4]={0x00,0x11,0x22,0x33}怎么转换成cstring输出00112233呢 解决方案 char r[8]; for (int i = 0; i < 4; i++) { r[i * 2] = (char)(ch[i] / 16 + '0'); r[i * 2 + 1] = (char)(ch[i] % 16 + '0'); } CString str = CString(r); 解决方案二: char r[8];

string与char*的转换(转载)

//string --> const char  string str2ch: str2ch.c_str();    //=============================   //string --> char * //先转为 const char , 然后 转char *    char TargetFile[strlen(TorrentFileNameDown.c_str())];     strcpy(TargetFile,TorrentFileNameDown.c_str()

c++-string字符串转char* 之后变成空字符串是怎么回事?

问题描述 string字符串转char* 之后变成空字符串是怎么回事? string字符串转char* 之后变成空字符串是怎么回事?百思不得其解 解决方案 你具体的代码是什么,string是否用的c_str()来转换的 解决方案二: 建议楼主贴一下转换char *的代码 解决方案三: string它转化为const char*的方法 string s1 = "hellow"; const char * char1 = s1.c_str(); 解决方案四: 代码在公司,我把形式大概列举一