在C中,是数组指针还是用作指针?

我的理解是,数组只是指向一系列值的常量指针,并且当您在C中声明一个数组时,您声明了一个指针并为它指向的序列分配空间。

但是这使我困惑:下面的代码:

char y[20]; char *z = y; printf("y size is %lu\n", sizeof(y)); printf("y is %p\n", y); printf("z size is %lu\n", sizeof(z)); printf("z is %p\n", z); 

当与苹果GCC编译给出以下结果:

 y size is 20 y is 0x7fff5fbff930 z size is 8 z is 0x7fff5fbff930 

(我的机器是64位,指针是8个字节长)。

如果'y'是一个常量指针,为什么它的大小是20,就像它指向的值序列一样? 在编译期间,variables名称“y”是否由内存地址replace? 那么,数组中的某种语法糖就是刚刚被编译时转换为指针的东西?

这是C标准( n1256 )的确切语言:

6.3.2.1左值,数组和函数指示符

3除了sizeof运算符的操作数或一元运算符的操作数,或者是用于初始化数组的string字面值以外,将types为'''的数组expression式转换为types为''指针的expression式键入 “'指向数组对象的初始元素,而不是一个左值。 如果数组对象具有寄存器存储类,则行为是未定义的。

这里要记住的重要一点是,一个对象 (用C语言来表示占用内存的东西)和用于引用该对象的expression式之间是有区别的。

当你声明一个数组如

 int a[10]; 

expression式 a指定的对象是一个数组(即,一个足够容纳10个int值的连续内存块),而expression式 a的types是“10个元素的int数组”或int [10] 。 如果expression式 a出现在不是作为sizeof&运算符的操作数的上下文中,则其types被隐式转换为int * ,其值是第一个元素的地址。

sizeof运算符的情况下,如果操作数是T [N]types的expression式,那么结果是数组对象中的字节数,而不是指向该对象的指针: N * sizeof T

&运算符的情况下,该值是数组的地址,它与数组的第一个元素的地址相同,但是expression式的types是不同的:给定声明T a[N]; ,expression式&a的types是T (*)[N] ,或者指向T的N元素的数组。该a或者&a[0] (数组的地址与地址相同数组中的第一个元素),但types的差异很重要。 例如,给定的代码

 int a[10]; int *p = a; int (*ap)[10] = &a; printf("p = %p, ap = %p\n", (void *) p, (void *) ap); p++; ap++; printf("p = %p, ap = %p\n", (void *) p, (void *) ap); 

你会看到输出的顺序

 p = 0xbff11e58, ap = 0xbff11e58 p = 0xbff11e5c, ap = 0xbff11e80 

IOW,前进psizeof int (4)的值增加到原始值,而推进ap增加了10 * sizeof int (40)的值。

更多标准语言:

6.5.2.1数组下标

约束

1其中一个expression式应该有'指向对象types'的指针,另一个expression式应该是整数types,结果是' type ' types

语义

2后缀expression式后面跟着方括号[]的expression式是数组对象元素的下标。 下标运算符[]的定义是E1[E2](*((E1)+(E2))) 。 由于适用于二元运算符的转换规则,如果E1是一个数组对象(等价地,指向一个数组对象的初始元素的指针), E2是一个整数, E1[E2]指定E2E2个元素E1 (从零开始计数)。

因此,当你为一个数组expression式下标时,发生的情况是数组中第一个元素的地址偏移量被计算出来,结果被解除引用。 expression方式

 a[i] = 10; 

相当于

 *((a)+(i)) = 10; 

相当于

 *((i)+(a)) = 10; 

相当于

  i[a] = 10; 

是的,C中的数组下标是可交换的; 为了上帝的爱,决不要在生产守则中这样做。

由于数组下标是根据指针操作定义的,因此可以将下标运算符应用于指针types的expression式以及数组types:

 int *p = malloc(sizeof *p * 10); int i; for (i = 0; i < 10; i++) p[i] = some_initial_value(); 

这里有一个方便的表格来记住这些概念:

声明:T a [N];

expression式types转换为值
 ---------- ---- ------------ -----
         一个T [N] T *地址中的第一个元素;
                                        与写作相同[0]
         &一个T(*)[N]数组的地址; 价值是一样的
                                        如上所述,但types不同
   sizeof size_t数组中包含的字节数
                                        对象(N * sizeof T)
         *一个[0]的T值
       a [i] T值在[i]
      &a [i] T *地址[i] 

声明:T a [N] [M];

expression式types转换为值
 ---------- ---- ------------ -----
          一个T [N] [M] T(*)[M]第一个子数组的地址(&a [0])
          &T(*)[N] [M]数组地址(与
                                             以上,但不同的types)
    sizeof size_t中包含的字节数
                                             数组对象(N * M * sizeof T)
          * a T [M] T *作为地址的[0]的值
                                             第一个子数组的第一个元素
                                              (与&a [0] [0]相同)
        a [i] T [M] T * a [i]的值,即地址
                                             我的子arrays的第一个元素
       &a [i] T(*)[M]第i个子数组的地址; 相同的价值
                                             以上,但types不同
 sizeof a [i] size_t包含在第i个子数组中的字节数
                                             对象(M * sizeof T)
       * i [i] T的第一个元素的值 
                                             子arrays(a [i] [0])
     a [i] [j] T值在a [i] [j]
    &a [i] [j] T * [i] [j]

声明:T a [N] [M] [O];

expression式types转换为
 ---------- ---- -----------
         一个T [N] [M] [O] T(*)[M] [O]
         &a T(*)[N] [M] [O]
         * a T [M] [O] T(*)[O]
       a [i] T [M] [O] T(*)[O]
      &a [i] T(*)[M] [O]
      * a [i] T [O] T *
    a [i] [j] T [O] T *
   &a [i] [j] T(*)[O]
   * a [i] [j] T 
 a [i] [j] [k] T

从这里开始,高维数组的模式应该是清晰的。

所以,总之:数组不是指针。 在大多数情况下,数组expression式被转换为指针types。

数组不是指针,尽pipe在大多数expression式中,数组名称的计算结果是指向数组的第一个元素的指针。 所以使用数组名称作为指针是非常非常容易的。 你会经常看到用来描述这个的“衰变”这个词,就像“arrays衰变成指针”一样。

一个例外是作为sizeof运算符的操作数,其中结果是数组大小(以字节为单位,而不是元素)。

还有一些与此有关的问题:

函数的数组参数是虚构的 – 编译器确实传递了一个普通指针(这不适用于C ++中的引用数组参数),所以你不能确定传递给函数的数组的实际大小 – 你必须以其他方式传递这些信息(也许使用一个明确的附加参数,或者使用一个sentinel元素 – 就像Cstring一样)

另外,获取数组中元素个数的常见习惯用法是使用如下macros:

 #define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0])) 

这有一个接受数组名称,它将工作的地方或者一个指针的问题,它会在没有编译器警告的情况下给出一个无意义的结果。 存在更安全的macros版本(特别是C ++),当它与指针而不是数组一起使用时,会生成警告或错误。 请参阅以下SO项目:

  • C ++版本
  • 一个更好的(虽然还不是非常安全的)C版本

注意:C99 VLAs(可变长度数组)可能不遵循所有这些规则(特别是,它们可以作为被调用函数已知数组大小的parameter passing)。 我对VLA几乎没有什么经验,据我所知他们没有被广泛使用。 不过,我想指出的是,上述讨论可能适用于VLA。

sizeof是在编译时计算的,编译器知道操作数是数组还是指针。 对于数组,它给出了数组占用的字节数。 你的数组是一个char[]sizeof(char)是1),所以sizeof恰好给你一些元素。 为了得到一般情况下的元素个数,一个常见的习惯用法是(这里是int ):

 int y[20]; printf("number of elements in y is %lu\n", sizeof(y) / sizeof(int)); 

对于指针sizeof给出了原始指针types占用的字节数。

除了别人说的外,也许这篇文章有助于: http : //en.wikipedia.org/wiki/C_%28programming_language%29#Array-pointer_interchangeability

 char hello[] = "hello there" int i; 

 char* hello = "hello there"; int i; 

在第一种情况下(折扣alignment),将存储12个字节用于hello,将分配的空间初始化为hello,而在第二个hello中则存储在别处(可能是静态空间), hello被初始化为指向给定的string。

然而, hello[2]以及*(hello + 2)会在两个实例中返回'e'。

如果'y'是一个常量指针,为什么它的大小是20,就像它指向的值序列一样?

因为z是variables的地址,所以你的机器总是返回8。 您需要使用解引用指针(&)来获取variables的内容。

编辑:两者之间的一个很好的区别: http : //www.cs.cf.ac.uk/Dave/C/node10.html