我的理解是,数组只是指向值序列的常量指针,当您在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中的某种语法糖,在编译时会转化为指针吗?

评论

该站点C ++常见问题的可能重复问题:数组名称是C中的指针吗?

#1 楼

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


6.3.2.1左值,数组和函数指示符
...
3除sizeof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串文字,将类型为“ array of type”的表达式转换为类型为“ pointer to type”的表达式,指向数组对象的初始元素,并且不是左值。如果数组对象具有寄存器存储类,则行为是不确定的。


这里要记住的重要一点是,对象之间存在差异(用C表示,这意味着需要

声明诸如

int a[10];
之类的数组时,

由对象指定的对象。表达式a是一个数组(即,一个足以容纳10个int值的连续内存块),表达式a的类型是“ int的10个元素的数组”或int [10]。如果表达式a出现在sizeof&运算符的操作数之外的上下文中,则其类型隐式转换为int *,并且其值是第一个元素的地址。

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

对于&运算符,其值是数组的地址,与数组的第一个元素的地址相同,但是表达式的类型不同:给定声明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);


,您将按以下顺序查看输出:

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


IOW ,前进psizeof int(4)添加到原始值,而前进ap10 * sizeof int(40)添加到原始值。

更多标准语言:

1其中一个表达式的类型应为“指向对象类型的指针”,语义
2在后缀表达式后跟方括号[]的表达式是数组对象元素的下标名称。下标运算符[]的定义是E1[E2](*((E1)+(E2)))相同。由于适用于二进制+运算符的转换规则,如果E1是数组对象(相当于指向数组对象初始元素的指针),而E2是整数,则E1[E2]指定E2的第E1个元素(计数)从零开始)。


因此,当您给数组表达式下标时,实际上是从数组中第一个元素的地址开始计算偏移量,结果是取消引用。表达式

a[i] = 10;


等效于

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


等效于

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


等效于

 i[a] = 10;


是的,C语言中的数组下标是可交换的;为了上帝的爱,永远不要在生产代码中这样做。
由于数组下标是根据指针操作定义的,因此可以将下标运算符应用于指针类型和数组类型的表达式:

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


这里有一个方便的表格,可以记住其中的一些概念:

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


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

因此,总而言之:数组不是指针。在大多数情况下,数组表达式都会转换为指针类型。

#2 楼

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

一个例外是sizeof运算符的操作数,其结果是大小数组的字节数(以字节为单位,而不是元素)。与此相关的另外两个问题:

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

此外,获取数组中元素数量的常见用法是使用类似以下的宏:

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


这有一个问题,即接受一个数组名称(在它可以工作的地方),或者一个指针(它在没有编译器警告的情况下给出无用的结果)。存在更安全的宏版本(尤其是对于C ++),该宏版本在与指针而不是数组一起使用时会生成警告或错误。请参见以下SO项:


C ++版本
更好的C版本(尽管仍然不是很安全)


注意:C99 VLA(可变长度数组)可能不遵循所有这些规则(特别是,可以将它们作为参数传递,并具有被调用函数知道的数组大小)。我对VLA的经验很少,据我所知,它们并未得到广泛使用。但是,我确实要指出,以上讨论可能对VLA有所不同。

评论


所以做了sizeof异常是因为它很有用...我不知道实际上有一种方法可以知道数组的大小! (尽管它仍然没有那么有用,因为它只能找到固定大小的数组的大小,但是我想比为相同的目的定义许多常量要好)

–萨尔瓦多p
2011年1月6日在18:17

#3 楼

sizeof在编译时求值,并且编译器知道操作数是数组还是指针。对于数组,它给出了数组占用的字节数。您的数组是一个char[](并且sizeof(char)为1),因此sizeof恰好为您提供了元素数量。要获取一般情况下的元素数量,一个常见的习惯用法是(对于int而言):

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


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

#4 楼



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




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


在第一个实例(折扣对齐)中,将存储12个字节用于你好,已分配的空间初始化为你好,而在第二个你好,存储在其他地方(可能是静态空间),并且hello初始化为指向给定的字符串。
但是在两种情况下都是e'。

#5 楼

除了其他人所说的之外,这篇文章也许还可以帮助您:http://en.wikipedia.org/wiki/C_%28programming_language%29#Array-pointer_interchangeability

#6 楼


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


因为z是变量,并将始终为您的计算机返回8。您需要使用取消引用指针(&)才能获取变量的内容。

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

评论


他正在询问y,而您正在询问关于z,这令人困惑。很清楚为什么z的大小为8。对此你没有答案。此外,&是C中的地址运算符;取消引用运算符为*。

–彼得-恢复莫妮卡
18年1月15日在8:07