什么是数组指针衰减?它和数组指针有关系吗?


当前回答

以下是该标准的内容(C99 6.3.3.1 /3 -其他操作数-左值、数组和函数指示符):

除非它是sizeof操作符或一元&操作符的操作数,或者是a 字符串字面值用于初始化数组,具有类型“数组类型”的表达式为 转换为类型为“指针指向类型”的表达式,该表达式指向的初始元素 数组对象和不是左值。

这意味着无论何时在表达式中使用数组名称,它都会自动转换为指向数组中第一项的指针。

请注意,函数名的作用与此类似,但函数指针的使用要少得多,而且使用的方式要专门得多,因此不会像将数组名自动转换为指针那样引起混乱。

c++标准(4.2数组到指针转换)将转换要求放宽为(强调我的):

类型为“N T的数组”或“未知T界的数组”的左值或右值可以转换为右值 类型为“指向t的指针”

因此转换不必像C中那样发生(这让函数重载或模板匹配数组类型)。

这也是为什么在C语言中你应该避免在函数原型/定义中使用数组形参(在我看来-我不确定是否有普遍的共识)。它们会引起混乱,而且无论如何都是虚构的——使用指针形参,混乱可能不会完全消失,但至少形参声明没有说谎。

其他回答

数组衰减意味着,当数组作为参数传递给函数时,它被视为(“衰减为”)指针。

void do_something(int *array) {
  // We don't know how big array is here, because it's decayed to a pointer.
  printf("%i\n", sizeof(array));  // always prints 4 on a 32-bit machine
}

int main (int argc, char **argv) {
    int a[10];
    int b[20];
    int *c;
    printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
    printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
    printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
    do_something(a);
    do_something(b);
    do_something(c);
}

上述情况有两个复杂情况或例外情况。

首先,在C和c++中处理多维数组时,只丢失了第一个维度。这是因为数组在内存中是连续布局的,所以编译器必须知道除第一个维度以外的所有维度,才能计算该内存块的偏移量。

void do_something(int array[][10])
{
    // We don't know how big the first dimension is.
}

int main(int argc, char *argv[]) {
    int a[5][10];
    int b[20][10];
    do_something(a);
    do_something(b);
    return 0;
}

其次,在c++中,您可以使用模板来推断数组的大小。微软将此用于c++版本的Secure CRT函数(如strcpy_s),您可以使用类似的技巧可靠地获取数组中的元素数量。

数组与C/ c++中的指针基本相同,但又不完全相同。一旦你转换一个数组:

const int a[] = { 2, 3, 5, 7, 11 };

转换为指针(不需要强制转换,因此在某些情况下可能会意外发生):

const int* p = a;

你将失去sizeof操作符计算数组中元素的能力:

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

这种丧失的能力被称为“衰退”。

有关更多详细信息,请参阅这篇关于数组衰减的文章。

"Decay" refers to the implicit conversion of an expression from an array type to a pointer type. In most contexts, when the compiler sees an array expression it converts the type of the expression from "N-element array of T" to "pointer to T" and sets the value of the expression to the address of the first element of the array. The exceptions to this rule are when an array is an operand of either the sizeof or & operators, or the array is a string literal being used as an initializer in a declaration.

假设有以下代码:

char a[80];
strcpy(a, "This is a test");

表达式a的类型是“80-element array of char”,表达式“This is a test”的类型是“15-element array of char”(在C语言中;在c++中,字符串字面值是const char数组)。然而,在对strcpy()的调用中,两个表达式都不是sizeof或&的操作数,因此它们的类型被隐式转换为“指向char的指针”,并且它们的值被设置为每个表达式中第一个元素的地址。strcpy()接收的不是数组,而是指针,正如它的原型所示:

char *strcpy(char *dest, const char *src);

这和数组指针不是一回事。例如:

char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;

ptr_to_first_element和ptr_to_array都有相同的值;a的基址。但它们是不同的类型,区别对待,如下图所示:

a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]

请记住,表达式a[i]被解释为*(a+i)(只有在数组类型转换为指针类型时才有效),因此a[i]和ptr_to_first_element[i]的工作原理相同。表达式(*ptr_to_array)[i]被解释为*(*a+i)。表达式*ptr_to_array[i]和ptr_to_array[i]可能会根据上下文导致编译器警告或错误;如果你期望它们的值是a[i],它们肯定会出错。

sizeof a == sizeof *ptr_to_array == 80

同样,当数组是sizeof的操作数时,它不会转换为指针类型。

sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
                                                  is on your platform

Ptr_to_first_element是一个简单的char指针。

当数组腐烂并被指向时;-)

实际上,如果你想传递一个数组到某个地方,但却传递了指针(因为谁会他妈的为你传递整个数组),人们会说这个可怜的数组衰减为指针。

试试这段代码


void f(double a[10]) {
    printf("in function: %d", sizeof(a));
    printf("pointer size: %d\n", sizeof(double *));
}

int main() {
    double a[10];
    printf("in main: %d", sizeof(a));
    f(a);
}

你会看到函数中数组的大小并不等于main中数组的大小,而是等于指针的大小。

你可能听说过“数组是指针”,但是,这不是完全正确的(sizeof在main打印正确的大小)。然而,当传递时,数组衰减为指针。也就是说,不管语法显示的是什么,实际上传递了一个指针,函数实际上接收了一个指针。

在这种情况下,定义void f(double a[10]被编译器隐式转换为void f(double *a)。你可以直接将函数参数声明为*a。您甚至可以编写一个[100]或[1],而不是[10],因为它实际上从未以这种方式编译(但是,您显然不应该这样做,这会使读者感到困惑)。