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


当前回答

试试这段代码


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],因为它实际上从未以这种方式编译(但是,您显然不应该这样做,这会使读者感到困惑)。

其他回答

tl;dr:当您使用已定义的数组时,实际上是在使用指向其第一个元素的指针。

因此:

当你写arr[idx]时,你实际上是在说*(arr + idx)。 函数从来不会真正将数组作为参数,只接受指针——当指定数组参数时可以直接接受,如果将引用传递给数组则可以间接接受。

这条规则的例外情况:

可以将固定长度的数组传递给结构中的函数。 Sizeof()给出数组占用的大小,而不是指针的大小。

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

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

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

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

据说数组会“衰减”成指针。声明为int numbers[5]的c++数组不能被重指向,即不能说numbers = 0x5a5aff23。更重要的是,衰减一词意味着类型和维度的丧失;数字通过丢失维数信息(计数5)衰减为int*,类型不再是int[5]。看看这里没有发生衰变的情况。

如果你是按值传递一个数组,你实际上是在复制一个指针——指向数组第一个元素的指针被复制到形参(形参的类型也应该是数组元素的类型的指针)。这是由于数组的衰减性质;一旦衰变,sizeof就不再给出整个数组的大小,因为它本质上变成了一个指针。这就是为什么首选通过引用或指针传递的原因(以及其他原因)。

传递数组1的三种方法:

void by_value(const T* array)   // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])

后两个将提供适当的sizeof信息,而第一个不会,因为数组参数已经衰减为赋值给形参。

常量U应该在编译时已知。

在C语言中,数组没有值。

如果需要对象的值,但对象是数组,则使用其第一个元素的地址,type pointer to(数组元素的类型)。

在函数中,所有参数都是按值传递的(数组也不例外)。当你在函数中传递一个数组时,它“衰减为指针”(原文如此);当你将一个数组与其他东西进行比较时,它再次“衰减为指针”(原文如此);...

void foo(int arr[]);

函数foo期望数组的值。但是,在C语言中,数组没有值!foo得到的是数组第一个元素的地址。

int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }

在上面的比较中,arr没有值,所以它成为一个指针。它变成了一个指向int的指针。该指针可以与变量ip进行比较。

在数组索引语法中你经常看到,arr被衰减为指针

arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */

只有当数组是sizeof操作符的操作数,或&操作符('address of'操作符)的操作数,或用作初始化字符数组的字符串字面值时,数组才不会衰减为指针。