什么是指针衰减数组?


384

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


73
鲜为人知:一元加运算符可以用作“衰减运算符”:给定int a[10]; int b(void);+a则为int指针,+b为函数指针。如果要将其传递给接受引用的模板,则很有用。
Johannes Schaub-litb

3
@litb-parens会做同样的事情(例如(a)应该是一个计算为指针的表达式),对吗?
Michael Burr

21
std::decay从C ++ 14开始,使用一元+衰减数组的方法不太那么晦涩。
legends2k 2015年

21
@ JohannesSchaub - litb因为这个问题被标记C和C ++,我想澄清的是,虽然+a+b在C是法律++,它是在C(非法C11 6.5.3.3/1“一元的操作+-操作员应算术类型”)
MM

5
@lege对。但是我想这与一元+的窍门不一样。我提到它的原因不仅仅是因为它会衰减,而且是因为它是一些有趣的东西;)
Johannes Schaub-litb 2015年

Answers:


283

据说数组会“衰减”到指针中。声明为的C ++数组int numbers [5]无法重新指向,即您不能说numbers = 0x5a5aff23。更重要的是,术语“衰减”表示类型和尺寸的损失。numbersint*因丢失尺寸信息(计数5)而衰减为1 ,类型不再存在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信息,而第一个将不提供信息,因为array参数已衰减为可分配给该参数。

1常数U在编译时应该是已知的。


8
如何通过价值传递?
rlbond

10
by_value正在将指针传递给数组的第一个元素;在函数参数的上下文中,T a[]与相同T *a。by_pointer传递相同的东西,除了指针值现在合格const。如果要传递指向数组的指针(与指向数组第一个元素的指针相反),则语法为T (*array)[U]
John Bode

4
“使用指向该数组的显式指针”-这是不正确的。如果a是的数组char,则a是类型char[N],并将衰变为char*;但&a类型为char(*)[N],并且不会衰减。
帕维尔·米纳夫

5
@FredOverflow:因此,如果U您进行更改,则不必记住在两个位置进行更改,否则就冒着无声错误的风险……自治!
Lightness Races in Orbit 2014年

4
“如果您要按值传递数组,那么您真正要做的就是复制一个指针”,这是没有意义的,因为数组不能按值,周期传递。
juanchopanza

103

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

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

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

const int* p = a;

您将失去sizeof运算符对数组中元素进行计数的能力:

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

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

有关更多详细信息,请查看有关数组衰减的本文


51
数组是不是基本相同的指针; 他们是完全不同的动物。在大多数情况下,数组可以处理,就好像它是一个指针,指针是可以治疗的,就好像它是一个数组,但这是尽可能靠近他们得到。
John Bode

20
@约翰,请原谅我的语言不准确。我一直在努力寻找答案,而又不会陷入冗长的背景故事中,而“基本上...但不太完全”是我上大学以来的一个很好的解释。我相信任何有兴趣的人都可以从您的评论中获得更准确的图片。
系统暂停

在谈论类型转换时,“无需铸造的作品”与“隐式发生”的含义相同
MM

47

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

除非它是sizeof运算符或一元&运算符的操作数,或者是用于初始化数组的字符串文字,否则将类型为“ array of type”的表达式转换为类型为“ pointer to to”的表达式类型'',它指向数组对象的初始元素,不是左值。

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

请注意,函数名称的作用方式相似,但是使用函数指针的方式少得多,而且使用的方式也更加专门化,不会像将数组名称自动转换为指针那样引起混乱。

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

左值或类型“数组NT的”或“结合的T未知的数组”的右值可以被转换成类型的右值“指针T.”

所以转换不会发生像它几乎总是用C做(这让函数重载或模板的阵列类型相匹配)。

这也是为什么在C语言中应避免在函数原型/定义中使用数组参数的原因(我认为-我不确定是否有任何一般性协议)。它们会引起混乱,并且仍然是虚构的-使用指针参数,混乱可能不会完全消失,但至少不会声明参数声明。


2
在示例代码行中,“类型为'数组类型'的表达式”为“用于初始化数组的字符串文字”是什么?
加勒特2014年

4
@Garrett char x[] = "Hello";。6个元素的数组"Hello"不会衰减;而是x获取size,6并从的元素初始化其元素"Hello"
MM

30

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

假设以下代码:

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

表达式a的类型为“ 80个字符的char数组”,表达式“这是一个测试”的类型为“ 16个元素的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的简单指针。


1
是不是"This is a test" is of type "16-element array of char"一个"15-element array of char"?(长度14 + 1表示\ 0)
chux-恢复莫妮卡

16

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


5
“数组没有价值”-这意味着什么?当然阵列具有价值......他们的对象,你可以有一个指针,而在C ++中,对它们的引用,等等
帕维尔Minaev

2
我相信,严格地说,“值”在C中定义为根据类型对对象的位的解释。我很难弄清楚数组类型的有用含义。相反,您可以说您转换为指针,但这并不是在解释数组的内容,它只是获取其位置。您得到的是指针的值(它是一个地址),而不是数组的值(这将是“所包含项目的值序列”,如“字符串”的定义中所使用)。就是说,我认为当一个表示指针获得时,说“数组的值”是很公平的。
Johannes Schaub-litb

无论如何,我认为还是有一点模糊性:对象的值和表达式的值(如“ rvalue”)。如果用后一种方式解释,则数组表达式肯定具有一个值:它是将其衰减为rvalue所得的值,并且是指针表达式。但是,如果以前一种方式进行解释,那么对于数组对象当然没有任何有用的意义。
Johannes Schaub-litb

1
对该词组+1,但修正不大;对于数组,它甚至不是三联体,也不仅仅是对联[location,type]。对于阵列盒的第三个位置,您还有其他想法吗?我什么也想不到。
legends2k 2014年

1
@ legends2k:我想我在数组中使用了第三个位置,以避免使它们成为仅具有对联的特殊情况。也许[location,type,void ]会更好。
2014年

8

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

实际上,只是如果您想在某个地方传递一个数组,但是要传递指针(因为谁会为您传递整个数组),人们说可怜的数组会退化为指针。


很好说。什么是不会衰减到指针的良好数组或防止其衰减的数组?您可以在C中举一个例子吗?谢谢。
Unheilig 2014年

@Unheilig,可以将数组真空包装到结构中并传递该结构。
Michael Krelin-黑客2014年

我不确定“工作”是什么意思。不允许访问该数组,尽管如果您期望实际发生的事情可以按预期运行。该行为(尽管再次官方未定义)得以保留。
Michael Krelin-黑客2014年

衰减也会在许多没有传递数组的情况下发生(如其他答案所述)。例如,a + 1
MM

3

数组衰减意味着,将数组作为参数传递给函数时,将其等同于(“衰减为”)指针。

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将其用于诸如strcpy_s之类的Secure CRT函数的C ++版本,并且您可以使用类似的技巧来可靠地获取数组中的元素数


1
衰减发生在许多其他情况下,而不仅仅是将数组传递给函数。
MM

0

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

从而:

  • 当你写的时候arr[idx]你真的是在说*(arr + idx)
  • 函数永远不会真正将数组作为参数,仅将指针作为指针,即使您指定了数组参数也是如此。

此规则的排序例外:

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

0

我可能非常大胆地认为有四(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 ++本机数组处理是关键任务语言功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.