什么是阵列衰减?

时间:2009-09-22 17:24:43

标签: c++ c arrays pointers

什么是阵列的衰减?与数组指针有关系吗?

10 个答案:

答案 0 :(得分:242)

据说数组“衰变”为指针。声明为int numbers [5]的C ++数组无法重新指向,即您不能说numbers = 0x5a5aff23。更重要的是,衰变一词意味着类型和维度的损失; numbers通过丢失维度信息(计数5)而衰减到int*,类型不再是int [5]。在这里查看cases where the decay doesn't happen

如果你按值传递数组,那么你真正在做的是复制指针 - 指向数组的第一个元素的指针被复制到参数(其类型也应该是数组元素类型的指针)。这是因为阵列的衰变性质;一旦衰减,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信息,而第一个不会,因为数组参数已经衰减并被赋值给参数。

1常量U应在编译时知道。

答案 1 :(得分:88)

数组与C / C ++中的指针基本相同,但并不完全相同。转换数组后:

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

指向一个指针(在没有强制转换的情况下工作,因此在某些情况下可能会意外发生):

const int* p = a;

你失去了sizeof运算符计算数组中元素的能力:

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

这种失去的能力被称为“衰变”。

有关详细信息,请查看此article about array decay

答案 2 :(得分:43)

这是标准所说的内容(C99 6.3.2.1/3 - 其他操作数 - 左值,数组和函数指示符):

  

除非它是sizeof运算符或一元&的操作数。运营商,或者是   用于初始化数组的字符串文字,类型为''数组类型'的表达式是   转换为类型为''指向类型'的指针的表达式,指向的初始元素   数组对象并不是左值。

这意味着几乎在表达式中使用数组名称时,它会自动转换为指向数组中第1项的指针。

请注意,函数名称的作用方式类似,但函数指针的使用方式要少得多,并且使用方式更加专业,因为它不会引起与将数组名称自动转换为指针一样多的混淆。

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

  

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

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

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

答案 3 :(得分:25)

“Decay”是指表达式从数组类型到指针类型的隐式转换。在大多数情况下,当编译器看到数组表达式时,它将表达式的类型从“N元素数组T”转换为“指向T”,并将表达式的值设置为数组第一个元素的地址。此规则的例外情况是,数组是sizeof&运算符的操作数,或者数组是在声明中用作初始值设定项的字符串文字。

假设以下代码:

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

表达式a的类型为“80-element array of char”,表达式“This is a test”的类型为“16-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_elementptr_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的简单指针。

答案 4 :(得分:12)

C中的数组没有任何价值。

只要期望对象的值但对象是数组,则使用其第一个元素的地址,类型为pointer to (type of array elements)

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

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运算符的操作数,或者& operator('运算符的地址),或者用作初始化字符数组的字符串文字。

答案 5 :(得分:6)

当阵列腐烂并被指向时; - )

实际上,只是如果你想在某个地方传递一个数组,而是传递指针(因为到底谁会为你传递整个数组),人们会说糟糕的数组会衰减到指针。

答案 6 :(得分:2)

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

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 ++中,您可以使用模板来推断数组的大小。 Microsoft将此用于安全CRT函数的C ++版本,如strcpy_s,您可以使用类似的技巧来get the number of elements in an array

答案 7 :(得分:0)

tl; dr:当你使用你定义的数组时,你实际上将使用指向它的第一个元素的指针。

因此:

  • 当你写arr[idx]时,你真的只是说*(arr + idx)
  • 函数从不真正将数组作为参数,仅指针,即使指定了数组参数。

此规则的排序例外:

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

答案 8 :(得分:0)

我可能很勇敢地认为有四(4)种方法可以将数组作为函数参数传递。这也是供您细读的简短但有效的代码。

0000:00:0:01:00

我也可能认为这表明了C ++与C的优越性。至少在引用(按双关语的意图)中通过引用传递数组。

当然,有非常严格的项目,没有堆分配,没有异常,也没有std :: lib。有人可能会说,C ++本机数组处理是关键任务语言功能。

答案 9 :(得分:0)

数组在 C 中由指针自动传递。The rationale behind it can only be speculated

int a[5]int *aint (*a)[5] 都是美化地址,这意味着编译器会根据类型对它们的算术和尊重运算符进行不同的处理,因此当它们引用相同的地址时,它们编译器不一样对待。 int a[5] 与其他 2 的不同之处在于地址是隐式的并且不作为数组本身的一部分出现在堆栈或可执行文件中,它仅被编译器用于解析某些算术运算,例如取其地址或指针算法。 int a[5] 因此既是一个数组又是一个隐式地址,但是一旦你谈到地址本身并将它放在堆栈上,地址本身就不再是一个数组,而只能是一个指向数组或衰减数组,即指向数组第一个成员的指针。

例如,在 int (*a)[5] 上,a 上的第一次取消引用将产生 int *(因此相同的地址,只是类型不同,注意不是 int a[5]) ,以及 a 上的指针运算,即 a+1*(a+1) 将根据 5 个整数数组的大小(这是它指向的数据类型),以及第二个取消引用将产生 int。然而,在 int a[5] 上,第一次取消引用将产生 int,指针算法将根据 int 的大小进行。

对于一个函数,你只能传递int *int (*)[5],并且函数会将它强制转换为任何参数类型,因此在函数中你可以选择是否处理一个地址为作为衰减数组或指向数组的指针传递(其中函数必须指定传递的数组的大小)。如果您将 a 传递给一个函数并且 a 被定义为 int a[5],那么当 a 解析为一个地址时,您正在传递一个地址,并且一个地址只能是一个指针类型。在函数中,它访问的参数是堆栈或寄存器中的地址,它只能是指针类型而不是数组类型——这是因为它是堆栈上的实际地址,因此显然不是数组本身。

您丢失了数组的大小,因为作为地址的参数类型是指针而不是数组,数组没有数组大小,如使用 sizeof 时所见,适用于传递给它的值的类型。参数类型 int a[5] 而不是 int *a 是允许的,但被视为 int * 而不是完全禁止,尽管它应该被禁止,因为它具有误导性,因为它使您认为可以使用大小信息,但只能通过将其强制转换为 int (*a)[5] 来实现,当然,函数必须指定数组的大小,因为无法传递数组的大小,因为数组的大小需要是编译时常量。