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

sal*_*r p 45 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);
Run Code Online (Sandbox Code Playgroud)

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

y size is 20
y is 0x7fff5fbff930
z size is 8
z is 0x7fff5fbff930
Run Code Online (Sandbox Code Playgroud)

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

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

Joh*_*ode 60

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

6.3.2.1左值,数组和功能指示器
...
3除了当它是的操作数sizeof操作者或一元&运算符,或者是用于初始化数组文本的字符串,其具有输入""的阵列的表达类型 ' '被转换成类型的表达式'’指针键入指向阵列对象的初始元素,不是左值'’.如果数组对象具有寄存器存储类,则行为未定义.

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

声明一个数组时

int a[10];
Run Code Online (Sandbox Code Playgroud)

表达式指定的对象是一个数组(即,一个足够大的内存块以容纳10个值),表达式 a 的类型是"10元素数组",或.如果表达出现在不是作为的操作数的其它的上下文或操作员,那么它的类型被隐式转换为,它的值是第一元素的地址. aintintint [10] asizeof&int *

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

对于&运算符,value是数组的地址,它与数组的第一个元素的地址相同,但表达式的类型不同:给定声明T a[N];,表达式的类型&aT (*)[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);
Run Code Online (Sandbox Code Playgroud)

你会看到订单上的输出

p = 0xbff11e58, ap = 0xbff11e58
p = 0xbff11e5c, ap = 0xbff11e80
Run Code Online (Sandbox Code Playgroud)

IOW,advance)psizeof int(4)添加到原始值,而advance advance 则ap增加10 * sizeof int(40).

更标准的语言:

6.5.2.1数组下标

约束

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

语义

2后缀表达式后跟方括号中的表达式[]是数组对象元素的下标名称.下标操作符的定义[]E1[E2]相同(*((E1)+(E2))).由于适用于二元+运算符的转换规则,if E1是一个数组对象(等效地,指向数组对象的初始元素的指针)并且E2是一个整数,因此E1[E2]指定E2-th元素E1(从零开始计数).

因此,当您下标一个数组表达式时,底层发生的事情是计算数组中第一个元素的地址的偏移量并取消引用结果.表达方式

a[i] = 10;
Run Code Online (Sandbox Code Playgroud)

相当于

*((a)+(i)) = 10;
Run Code Online (Sandbox Code Playgroud)

这相当于

*((i)+(a)) = 10;
Run Code Online (Sandbox Code Playgroud)

这相当于

 i[a] = 10;
Run Code Online (Sandbox Code Playgroud)

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

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

int *p = malloc(sizeof *p * 10);
int i;
for (i = 0; i < 10; i++)
  p[i] = some_initial_value(); 
Run Code Online (Sandbox Code Playgroud)

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

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

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

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


Mic*_*urr 24

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

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

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

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

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

#define ARRAY_SIZE(arr) ((sizeof(arr))/sizeof(arr[0]))
Run Code Online (Sandbox Code Playgroud)

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


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


Pét*_*rök 6

sizeof在编译时计算,编译器知道操作数是数组还是指针.对于数组,它给出了数组占用的字节数.你的数组是char[](并且sizeof(char)是1),因此sizeof碰巧给你了元素的数量.为了获得一般情况下的元素数量,一个常见的习语是(这里int):

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

对于指针,sizeof给出了原始指针类型占用的字节数.