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

时间:2011-01-05 17:26:36

标签: c arrays pointers

我的理解是数组只是指向一系列值的常量指针,当你在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);

使用Apple GCC编译时会得到以下结果:

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

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

如果'y'是常量指针,为什么它的大小为20,就像它指向的值序列一样?变量名'y'是否在编译时被内存地址替换为适当的?那么数组是否是C中的某种语法糖,它在编译时只是转换为指针的东西?

6 个答案:

答案 0 :(得分:55)

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

6.3.2.1左值,数组和函数指示符
...
3除非它是sizeof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串文字,否则表达式的类型为''数组 type < / em>''被转换为类型为''指向 type '的指针的表达式,它指向数组对象的初始元素,而不是左值。如果数组对象具有寄存器存储类,则行为未定义。

要记住的重要一点是对象(用C语言表示占用内存的东西)和用于引用的表达式之间存在差异那个对象。

声明诸如

之类的数组时
int a[10];
表达式 a指定的对象是一个数组(即,一个足够大的内存块,可容纳10 int个值),表达式的类型是“int”的10元素数组,或int [10]。如果表达式 a出现在除sizeof&运算符的操作数之外的上下文中,则其类型将隐式转换为{{1} },它的值是第一个元素的地址。

对于int *运算符,如果操作数是类型sizeof的表达式,则结果是数组对象中的字节数,而不是指向该对象的指针: T [N]

对于N * sizeof T运算符,该值是数组的地址,它与数组的第一个元素的地址相同,但类型为表达式是不同的:给定声明&,表达式T a[N];的类型是&a,或指向T的N元素数组的指针。T (*)[N]a相同(数组的地址与数组中第一个元素的地址相同),但类型的差异很重要。例如,给定代码

&a[0]

您将看到

顺序的输出
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);

IOW,推进p = 0xbff11e58, ap = 0xbff11e58 p = 0xbff11e5c, ap = 0xbff11e80 p(4)添加到原始值,而推进sizeof int则添加ap(40)。

更多标准语言:

6.5.2.1数组下标

约束

1其中一个表达式应具有类型''指向对象类型''的指针,另一个表达式应具有整数类型,结果类型为'' type ''。

语义

2后缀表达式后跟方括号10 * sizeof int中的表达式是数组对象元素的下标。下标运算符[]的定义是[]E1[E2]相同。由于适用于二进制(*((E1)+(E2)))运算符的转换规则,如果+是一个数组对象(等效地,指向数组对象的初始元素的指针),并且E1是一个整数,E2指定E1[E2]的{​​{1}}个元素(从零开始计数)。

因此,当你下标一个数组表达式时,幕后发生的是计算数组中第一个元素地址的偏移量并取消引用结果。表达式

E2

相当于

E1

相当于

a[i] = 10;

相当于

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

是的,C中的数组下标是可交换的;为了上帝的爱,永远不要在生产代码中这样做。

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

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

这是一个方便的表格来记住其中的一些概念:

Declaration: T a[N];

Expression    Type    Converts to     Value
----------    ----    ------------    -----
         a    T [N]   T *             Address of the first element in a;
                                        identical to writing &a[0]
        &a    T (*)[N]                Address of the array; value is the same
                                        as above, but the type is different
  sizeof a    size_t                  Number of bytes contained in the array
                                        object (N * sizeof T)
        *a    T                       Value at a[0]
      a[i]    T                       Value at a[i]
     &a[i]    T *                     Address of a[i] 

Declaration: T a[N][M];

Expression     Type        Converts to     Value
----------     ----        ------------    -----
          a    T [N][M]    T (*)[M]        Address of the first subarray (&a[0])
         &a    T (*)[N][M]                 Address of the array (same value as
                                             above, but different type)
   sizeof a    size_t                      Number of bytes contained in the
                                             array object (N * M * sizeof T)
         *a    T [M]      T *              Value of a[0], which is the address
                                             of the first element of the first subarray
                                             (same as &a[0][0])
       a[i]    T [M]      T *              Value of a[i], which is the address
                                             of the first element of the i'th subarray
      &a[i]    T (*)[M]                    Address of the i-th subarray; same value as
                                             above, but different type
sizeof a[i]    size_t                      Number of bytes contained in the i'th subarray
                                             object (M * sizeof T)
      *a[i]    T                           Value of the first element of the i'th 
                                             subarray (a[i][0])
    a[i][j]    T                           Value at a[i][j]
   &a[i][j]    T *                         Address of a[i][j]

Declaration: T a[N][M][O];

Expression        Type             Converts to
----------        ----             -----------
         a        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

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

因此,总结一下:数组不是指针。在大多数情况下,数组表达式将转换为指针类型。

答案 1 :(得分:23)

数组不是指针,但在大多数表达式中,数组名称的计算结果为指向数组第一个元素的指针。因此,使用数组名称作为指针非常非常容易。您经常会看到用于描述这个术语的“衰变”,如“数组衰减为指针”。

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

与此相关的另外几个问题:

函数的数组参数是虚构的 - 编译器实际上传递了一个普通指针(这不适用于C ++中的引用数组参数),因此无法确定传递给数组的数组的实际大小函数 - 你必须以其他方式传递该信息(可能使用显式的附加参数,或使用sentinel元素 - 如C字符串那样)

另外,获取数组中元素数量的常用习惯是使用如下宏:

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

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


注意:C99 VLA(可变长度数组)可能不遵循所有这些规则(特别是,它们可以作为具有被调用函数已知的数组大小的参数传递)。我对VLA的经验很少,据我所知,它们没有被广泛使用。但是,我想指出上述讨论可能对VLA采用不同的方式。

答案 2 :(得分:6)

在编译时计算

sizeof,编译器知道操作数是数组还是指针。对于数组,它给出了数组占用的字节数。您的数组是char[](而sizeof(char)是1),因此sizeof恰好为您提供了元素数量。为了获得一般情况下的元素数量,常见的习语是(此处为int):

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

指针sizeof给出原始指针类型占用的字节数。

答案 3 :(得分:1)

答案 4 :(得分:1)

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

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

在第一个实例中(折扣对齐)将为hello存储12个字节,其中已分配的空间初始化为 hello there ,而在第二个 hello中,存储在其他地方(可能是静态空间)并初始化hello以指向给定的字符串。

然而,

hello[2]以及*(hello + 2)将在两个实例中都返回“e”。

答案 5 :(得分:-2)

  

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

因为z是变量的地址,并且将始终为您的机器返回8。您需要使用取消引用指针(&amp;)来获取变量的内容。

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