我的理解是数组只是指向一个值序列的常量指针,当你在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标准(n1256)中的确切语言:

6.3.2.1 Lvalues, arrays, and function designators
...
3 Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘pointer to type’’ that points to the initial element of the array object and is not an lvalue. If the array object has register storage class, the behavior is undefined.

这里需要记住的重要一点是,object(在C术语中,表示占用内存的内容)与用于引用该对象的expression之间存在差异.

当您声明一个数组时,例如

int a[10];

expression a指定的object是一个数组(即,一个连续的内存块,其大小足以容纳10个int值),expression a的类型是"int的10元素数组",或int [10].如果expression a不是作为sizeof&运算符的操作数出现在上下文中,则其类型将隐式转换为int *,其值是第一个元素的地址.

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

&运算符的情况下,该值是数组的地址,与数组第一个元素的地址相同,但表达式的type不同:给定声明T a[N];,表达式&a的类型是T (*)[N]valuea&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

从表面上看,推进p会使原始值增加sizeof int(4),而推进ap会使原始值增加10 * sizeof int(40).

更标准的语言:

6.5.2.1 Array subscripting

Constraints

1 One of the expressions shall have type ‘‘pointer to object type’’, the other expression shall have integer type, and the result has type ‘‘type’’.

Semantics

2 A postfix expression followed by an expression in square brackets [] is a subscripted designation of an element of an array object. The definition of the subscript operator [] is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th element of E1 (counting from zero).

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

a[i] = 10;

相当于

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

which 相当于

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

which 相当于

 i[a] = 10;

是的,C中的数组下标是可交换的;看在god 的份上,千万不要在生产代码中这样做.

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

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

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

总之,数组不是指针.在大多数情况下,数组expressions被转换为指针类型.

C++相关问答推荐

va_arg -Linux和Windows上的不同行为

如何在C中的空指针函数中传递浮点值

Char变量如何在不使用方括号或花括号的情况下存储字符串,以及它如何迭代到下一个字符?

struct -未知大小

什么是.c.h文件?

Square不与Raylib一起移动

如何使用FSeek和文件流指针在C中查找文件的前一个元素和前一个减go 一个元素

';\n&39;和';\r&39;中的';\n&39;之间有什么关系?

C标准关于外部常量的说明

如何在不读取整个字符串的情况下删除UTF8字符串的尾随空格以提高性能?

如何摆脱-WIMPLICIT-Function-声明

GETS()在C++中重复它前面的行

Tcl_GetDoubleFromObj在列表的迭代中是一个缺点

哪些C++功能可以在外部C块中使用

当 n 是我们从用户那里获得的整数时,创建 n 个 struct 参数

这些表达式是否涉及 C 中定义的复合文字?

如何修复数组数据与列标题未对齐的问题?

free后内存泄漏?

全局变量 y0 与 mathlib 冲突,无法编译最小的 C 代码

可以从指针数组中的值初始化指针吗?