为什么数组可以直接赋值?
Why can arrays be assigned directly?
考虑这个代码片段:
void foo(int a[], int b[]){
static_assert(sizeof(a) == sizeof(int*));
static_assert(sizeof(b) == sizeof(int*));
b = a;
printf("%d", b[1]);
assert(a == b); // This also works!
}
int a[3] = {[1] = 2}, b[1];
foo(a, b);
输出(无编译错误):
2
我不明白为什么 b = a
有效。尽管数组可能会衰减为指针,但它们不应该衰减为 const 指针吗 (T * const
)?
他们不能。
数组无法分配给。 foo
函数中没有数组。函数参数列表中的语法 int a[]
表示声明 a
的类型为 "pointer to int
"。行为与代码 void foo(int *a, int *b)
完全相同。 (C11 6.7.6.3/7)
将一个指针赋给另一个指针是有效的。结果是两个指针都指向同一个位置。
Even though arrays may decay to pointers, shouldn't they decay to const pointers (T * const)?
从数组 "decay" 得到的指针是一个右值。 const
限定符仅对左值有意义 (C11 6.7.3/4)。 ("decay"指的是参数的转换,不是参数的调整)
引用 C11
,章节 §6.7.6.3,函数声明符(包括原型)
A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to
type’’, where the type qualifiers (if any) are those specified within the [
and ]
of the
array type derivation. [...]
所以,a
和b
其实是指针,不是数组。
这里没有对任何数组类型进行赋值,因此代码没有问题。
是的,将使用 []
声明的数组参数调整为 const 限定指针是有意义的。但是,const
在建立此行为时并不存在。
在开发 C 语言时,通过传递数组的地址来传递数组是有意义的,或者更具体地说,是第一个元素的地址。您当然不想复制整个数组来传递它。传递地址是使数组为被调用函数所知的一种简单方法。 (我们在 C++ 中看到的引用类型的语义尚未发明。)为了让程序员更容易,以便他们可以编写 foo(ArrayA, ArrayB)
而不是 foo(&Array[0], &ArrayB[0])
,将数组转换为的机制发明了指向其第一个元素的指针。 (根据 Dennis M. Ritchie M.M. and The Development of the C Language,这种参数表示法已经存在于 C 的前身语言 B 中。)
很好,您隐藏了转换。但这只是调用函数的地方。在被调用的例程中,正在考虑传递数组的程序员将要编写void foo(int ArrayA[], int ArrayB[])
。但由于我们实际上传递的是指针,而不是数组,因此需要将这些更改为 int *ArrayA
和 int *ArrayB
。因此创建了声明为数组的参数自动调整为指针的概念。
如您所见,这使程序员能够为参数赋值,从而改变数组的表观基址。将声明为 int ArrayA[]
的参数调整为 int * const ArrayA
是有意义的,这样就无法更改参数 ArrayA
的值。然后它会更像一个数组,其地址也不能更改,所以这更符合即使我们传递地址也假装传递数组的目标。
然而,当时const
并不存在,所以这是不可能的,当时也没有人想过发明const
(或者至少做了足够的努力得到它采用了语言)。
现在世界上有大量的源代码可以使用非常量调整。现在更改C语言的规范会导致现有代码出现问题。
考虑这个代码片段:
void foo(int a[], int b[]){
static_assert(sizeof(a) == sizeof(int*));
static_assert(sizeof(b) == sizeof(int*));
b = a;
printf("%d", b[1]);
assert(a == b); // This also works!
}
int a[3] = {[1] = 2}, b[1];
foo(a, b);
输出(无编译错误):
2
我不明白为什么 b = a
有效。尽管数组可能会衰减为指针,但它们不应该衰减为 const 指针吗 (T * const
)?
他们不能。
数组无法分配给。 foo
函数中没有数组。函数参数列表中的语法 int a[]
表示声明 a
的类型为 "pointer to int
"。行为与代码 void foo(int *a, int *b)
完全相同。 (C11 6.7.6.3/7)
将一个指针赋给另一个指针是有效的。结果是两个指针都指向同一个位置。
Even though arrays may decay to pointers, shouldn't they decay to const pointers (T * const)?
从数组 "decay" 得到的指针是一个右值。 const
限定符仅对左值有意义 (C11 6.7.3/4)。 ("decay"指的是参数的转换,不是参数的调整)
引用 C11
,章节 §6.7.6.3,函数声明符(包括原型)
A declaration of a parameter as ‘‘array of type’’ shall be adjusted to ‘‘qualified pointer to type’’, where the type qualifiers (if any) are those specified within the
[
and]
of the array type derivation. [...]
所以,a
和b
其实是指针,不是数组。
这里没有对任何数组类型进行赋值,因此代码没有问题。
是的,将使用 []
声明的数组参数调整为 const 限定指针是有意义的。但是,const
在建立此行为时并不存在。
在开发 C 语言时,通过传递数组的地址来传递数组是有意义的,或者更具体地说,是第一个元素的地址。您当然不想复制整个数组来传递它。传递地址是使数组为被调用函数所知的一种简单方法。 (我们在 C++ 中看到的引用类型的语义尚未发明。)为了让程序员更容易,以便他们可以编写 foo(ArrayA, ArrayB)
而不是 foo(&Array[0], &ArrayB[0])
,将数组转换为的机制发明了指向其第一个元素的指针。 (根据 Dennis M. Ritchie M.M. and The Development of the C Language,这种参数表示法已经存在于 C 的前身语言 B 中。)
很好,您隐藏了转换。但这只是调用函数的地方。在被调用的例程中,正在考虑传递数组的程序员将要编写void foo(int ArrayA[], int ArrayB[])
。但由于我们实际上传递的是指针,而不是数组,因此需要将这些更改为 int *ArrayA
和 int *ArrayB
。因此创建了声明为数组的参数自动调整为指针的概念。
如您所见,这使程序员能够为参数赋值,从而改变数组的表观基址。将声明为 int ArrayA[]
的参数调整为 int * const ArrayA
是有意义的,这样就无法更改参数 ArrayA
的值。然后它会更像一个数组,其地址也不能更改,所以这更符合即使我们传递地址也假装传递数组的目标。
然而,当时const
并不存在,所以这是不可能的,当时也没有人想过发明const
(或者至少做了足够的努力得到它采用了语言)。
现在世界上有大量的源代码可以使用非常量调整。现在更改C语言的规范会导致现有代码出现问题。