C的数组和指针都支持使用[]操作符进行下标操作, 但是最近学习编译原理, 对下标操作这样的表达式进行语义分析(主要是类型检查时), 发现了一个以前一直忽视的问题, 即对于连续的下标操作, 数组与多级指针的行为是不一样的.

C语言的数组是非动态的, 也就是说我们在编译期就可以知道关于这个数组的完整信息. 对于一个二维数组int A[m][n], A[2][3]表示*(&A[0][0] + (2 * n + 3) * sizeof(int)), 虽然有两个下标操作, 但是实际的解引用应该只有一次. 因为数组的内存布局是扁平的, 无论数组A有多少维, A, 作为一个变量名, 其代表的地址值(变量名可以理解做存储地址的别名)永远是第一个元素的地址, 区别只在于静态分析时的类型. 对于上述的二维数组A, 如果只进行一次下标操作, A[i], 那么A[i]的类型应该是int[n], 其值与A[i][0]依然是一致的 , 而对int**类型的指针来说, 一次下标操作就确确实实地从内存里里拿出了一个一个值, 并且类型退化为int*. 但是, 要想对这个一级的指针再做下标操作, 就要多掂量一番了.

所以, 当看到一个[]操作时, 要看它左边表达式的类型, 如果是大于一维的数组, 那么就只有指针的移动, 只有一维数组和指针, 才是偏移加解引用.

C语言结构体里的成员数组和指针中, s成员是指针就报错, 是数组就不会报错. 对此我的理解是:

  1. 变量是内存地址的别名
  2. 变量出现在语句中相当于对地址解引用
  3. 数组名是特殊的变量, 没有相应维数的下标操作的话就不会被解引用
  4. 不足维的数组下标操作只是地址偏移和类型退化

因此, 对数组成员的成员访问表达式直接返回偏移量, 而不会因为解引用这个过低的地址导致段错误.

[注] 类型退化是我生造的概念, 动机在于数组和指针这种类型是从基类型可以递归构造的, 每次下标或解引用操作都减少了该类型的递归深度, 更加接近基类型.