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


当前回答

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

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

其他回答

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

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

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

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

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

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

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

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

在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'操作符)的操作数,或用作初始化字符数组的字符串字面值时,数组才不会衰减为指针。

我可能会大胆地认为有四(4)种方法将数组作为函数参数传递。这里还有简短但可以工作的代码供您阅读。

#include <iostream>
#include <string>
#include <vector>
#include <cassert>

using namespace std;

// test data
// notice native array init with no copy aka "="
// not possible in C
 const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };

// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) { 
    // a pointer
    assert(array != nullptr); 
} ;

// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) { 
    // decayed to a pointer
    assert( array != nullptr ); 
}

// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
   // dealing with native pointer 
    assert( array != nullptr ); 
}

// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
    // array is not a pointer here
    // it is (almost) a container
    // most of the std:: lib algorithms 
    // do work on array reference, for example
    // range for requires std::begin() and std::end()
    // on the type passed as range to iterate over
    for (auto && elem : array )
    {
        cout << endl << elem ;
    }
}

int main()
{
     // ONE
     as_pointer(specimen);
     // TWO
     by_value_no_size(specimen);
     // THREE
     pointer_to_array(&specimen);
     // FOUR
     reference_to_array( specimen ) ;
}

我可能也认为这显示了c++相对于C的优势,至少在引用(双关语)通过引用传递数组。

当然,有些非常严格的项目没有堆分配,没有异常,也没有std:: lib。有人可能会说,c++原生数组处理是关键任务语言特性。

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),您可以使用类似的技巧可靠地获取数组中的元素数量。