提问人:Vamsi 提问时间:9/23/2009 最后编辑:Jan SchultkeVamsi 更新时间:9/26/2023 访问量:103358
什么是数组到指针转换又名。衰变?
What is array-to-pointer conversion aka. decay?
答:
这是数组腐烂并被指向的时候;-)
实际上,只是如果你想在某个地方传递一个数组,但指针被传递了(因为谁会为你传递整个数组),人们会说这个可怜的数组衰减为指针。
评论
a + 1
据说数组会“衰减”为指针。声明为 的 C++ 数组不能重新指向,即你不能说 .更重要的是,术语衰变表示类型和维度的丧失; 通过丢失维度信息(计数 5)而衰减,并且类型不再存在。请在此处查找不会发生衰变的情况。int numbers [5]
numbers = 0x5a5aff23
numbers
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
1 常量 U 在编译时应该是已知的。
评论
T a[]
T *a
const
T (*array)[U]
a
char
a
char[N]
char*
&a
char(*)[N]
U
数组与 C/C++ 中的指针基本相同,但不完全相同。转换数组后:
const int a[] = { 2, 3, 5, 7, 11 };
到指针(无需强制转换即可工作,因此在某些情况下可能会意外发生):
const int* p = a;
您将失去运算符对数组中的元素进行计数的能力:sizeof
assert( sizeof(p) != sizeof(a) ); // sizes are not equal
这种丧失的能力被称为“衰变”。
有关更多详细信息,请查看这篇关于阵列衰减的文章。
评论
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++ 中,您可以使用模板来推断数组的大小。Microsoft将其用于安全 CRT 函数(如 strcpy_s)的C++版本,您可以使用类似的技巧来可靠地获取数组中的元素数。
评论
在 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; */ }
在上面的比较中,没有值,所以它变成了一个指针。它成为指向 int 的指针。该指针可以与变量 进行比较。arr
ip
在你习惯看到的数组索引语法中,arr 再次“衰减为指针”
arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */
数组唯一不会衰减为指针的情况是,当它是 sizeof 运算符的操作数,或 & 运算符('address of' 运算符),或用作用于初始化字符数组的字符串文字时。
评论
标准是这样说的(C99 6.3.2.1/3 - 其他操作数 - 左值、数组和函数指示符):
除非它是 sizeof 运算符或一元 & 运算符的操作数,或者是 字符串文字,类型为“数组类型”的表达式为 转换为类型为“指针指向类型”的表达式,该表达式指向 数组对象,而不是左值。
这意味着,几乎每当在表达式中使用数组名称时,它都会自动转换为指向数组中第一项的指针。
请注意,函数名称的作用方式与此类似,但函数指针的使用要少得多,并且使用方式更加专业,因此它不会像将数组名称自动转换为指针那样造成混淆。
C++标准(4.2数组到指针的转换)将转换要求放宽为(强调我的):
“N T 数组”或“T 未知边界数组”类型的左左或右值可以转换为右值 类型为“指向 T 的指针”。
因此,转换不必像在 C 中那样发生(这允许函数重载或模板在数组类型上匹配)。
这也是为什么在 C 语言中你应该避免在函数原型/定义中使用数组参数(在我看来 - 我不确定是否有任何普遍的共识)。它们会引起混淆,无论如何都是虚构的 - 使用指针参数,混淆可能不会完全消失,但至少参数声明没有说谎。
评论
char x[] = "Hello";
"Hello"
x
6
"Hello"
“衰减”是指表达式从数组类型到指针类型的隐式转换。在大多数情况下,当编译器看到数组表达式时,它会将表达式的类型从“T 的 N 元素数组”转换为“指向 T 的指针”,并将表达式的值设置为数组第一个元素的地址。此规则的例外情况是,数组是 or 运算符的操作数,或者数组是用作声明中初始值设定项的字符串文本。sizeof
&
假设代码如下:
char a[80];
strcpy(a, "This is a test");
表达式的类型为“80 元素字符数组”,表达式“This is a test”的类型为“15 元素字符数组”(在 C 中;在 C++ 中,字符串文字是常量字符数组)。但是,在对 的调用中,这两个表达式都不是 或 的操作数,因此它们的类型被隐式转换为“指向 char 的指针”,并且它们的值被设置为每个元素中第一个元素的地址。接收的不是数组,而是指针,如其原型所示:a
strcpy()
sizeof
&
strcpy()
char *strcpy(char *dest, const char *src);
这与数组指针不是一回事。例如:
char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;
两者具有相同的值;A 的基址。但是,它们是不同的类型,处理方式也不同,如下所示:ptr_to_first_element
ptr_to_array
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 的简单指针。
评论
"This is a test" is of type "16-element array of char"
"15-element array of char"
*ptr_to_array
sizeof
&
tl;dr:当你使用你定义的数组时,你实际上会使用指向它的第一个元素的指针。
因此:
- 当你写作时,你真的只是在说.
arr[idx]
*(arr + idx)
- 函数从不真正将数组作为参数,而只将指针作为参数 - 在指定数组参数时直接接受,或者在传递对数组的引用时间接执行指针。
此规则的例外情况:
- 您可以将固定长度的数组传递给 .
struct
sizeof()
给出数组占用的大小,而不是指针的大小。
评论
sizeof
sizeof
我可能会大胆地认为有四 (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++ 原生数组处理是关键任务语言功能。
数组在 C 中通过指针自动传递,其背后的基本原理只能推测。
int a[5]
,并且都是美化地址,这意味着编译器根据类型以不同的方式处理它们的算术运算符和服从运算符,因此当它们引用相同的地址时,编译器不会对它们进行相同的处理。 与其他 2 个地址的不同之处在于,地址是隐式的,不会作为数组本身的一部分出现在堆栈或可执行文件中,它仅由编译器用于解析某些算术运算,例如获取其地址或指针算术。 因此,既是一个数组,也是一个隐式地址,但是一旦你谈论地址本身并将其放在堆栈上,地址本身就不再是一个数组,而只能是指向数组的指针或衰减数组,即指向数组第一个成员的指针。int *a
int (*a)[5]
int a[5]
int a[5]
例如,on ,第一个取消引用 on 将产生一个(所以相同的地址,只是不同的类型,请注意不是 ),以及指针算术 on 即 或者是 5 个整数数组的大小(这是它指向的数据类型),第二次取消引用将产生 .但是,第一次取消引用将产生 和 指针算术将根据 .int (*a)[5]
a
int *
int a[5]
a
a+1
*(a+1)
int
int a[5]
int
int
对于函数,您只能传递 和 ,并且该函数将其强制转换为任何参数类型,因此在函数中,您可以选择是将传递的地址视为衰减数组还是指向数组的指针(其中函数必须指定要传递的数组的大小)。如果传递给函数并被定义,则解析为地址,则传递的是地址,而地址只能是指针类型。在函数中,它访问的参数是堆栈或寄存器中的地址,它只能是指针类型,而不能是数组类型——这是因为它是堆栈上的实际地址,因此显然不是数组本身。int *
int (*)[5]
a
a
int a[5]
a
您丢失了数组的大小,因为参数的类型(地址)是指针而不是数组,数组没有数组大小,这在使用 时可以看出,它适用于传递给它的值的类型。参数类型 instead of 是允许的,但被视为而不是完全禁止它,尽管它应该被禁止,因为它具有误导性,因为它让你认为可以使用大小信息,但你只能通过将其转换为 来做到这一点,当然,函数必须指定数组的大小,因为没有办法传递数组的大小,因为大小的数组必须是编译时常量。sizeof
int a[5]
int *a
int *
int (*a)[5]
试试这段代码
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
在这种情况下,编译器将定义隐式转换为 。您可以等效地直接将函数参数声明为 。你甚至可以写 or 代替 ,因为它实际上从来没有以这种方式编译(但是,你不应该明显地这样做,它会让读者感到困惑)。void f(double a[10]
void f(double *a)
*a
a[100]
a[1]
a[10]
评论
int a[10]; int b(void);
+a
+b
std::decay
从 C++14 中可以减少数组在一元 + 上的衰减,这是一种不那么晦涩的方法。+a
+b
+
-