【问题标题】:C array declaration syntaxC 数组声明语法
【发布时间】:2018-12-24 18:38:11
【问题描述】:

在包含文件中声明一个数组,省略第一个维度大小:

extern float mvp[][4];

然后在翻译单元中的前面声明之后定义数组:

float mvp[4][4];

没问题。直到您尝试在包含第一个声明的文件中获取该数组的大小。然后你会得到:

error: invalid application of 'sizeof' to an incomplete type 'float [][4]'

我知道数组在用作左值时会衰减为指向其第一个元素的指针,函数原型中的数组声明实际上是变相的指针,但情况并非如此。但是第一个声明没有声明一个指针,它声明了一个“不完整的数组类型”,不同于:

extern float (*mvp)[4];

在声明变量时,编译器只引用一个“虚拟”基地址偏移量和链接器将解析的关联类型。

我想知道为什么这种“不完整的数组类型”——它不能像指向数组的指针那样递增,但也不是完全的数组,因为它的大小无法检索——会被允许存在?

为什么不将其隐式转换为指针(只是基地址偏移量)甚至更好,为什么不抛出错误以省略第一维的大小?


引用this

如果数组声明器中的表达式被省略,它声明一个未知大小的数组。除了在函数参数列表中(此类数组被转换为指针)和初始化器可用时,此类类型是不完整类型(请注意,未指定大小的 VLA,以 * 作为大小声明,是完整类型)

实际上,类型是不完整的,等待稍后的声明或暂定定义来完成。

【问题讨论】:

  • “我知道数组在用作左值时会衰减为指向其第一个元素的指针” - 我不这么认为。您可能的意思是当作为函数参数传递时它们会衰减为指针?
  • 当您使用数组的名称作为左值时,这意味着您想要引用内存存储而不是类型(并且您也不想创建右值)然后 数组将衰减为指向其第一个元素的指针.
  • @MichaelBeer:它们几乎在任何情况下都会衰减为指针(尽管不像问题描述的那样)。您正在考虑一种不同的机制,其中声明为数组类型的函数参数被自动定义为指针类型。
  • @user2357112 int b; int a[]; a = &b; - a 在这里用作左值,但兼容的编译器不应该允许这样做,恕我直言。
  • @MichaelBeer:是的,那是无效的。我正在考虑当您执行 pointer = arrayarray[5] 之类的操作时使数组衰减为指针的机制(是的,这涉及衰减),而您正在考虑将 int foo(int arg[5]) 转换为 int foo(int *arg) 的机制.

标签: c arrays pointers declaration


【解决方案1】:

使用extern 不会使事物存在,它只是用来说明某些事物可能存在于不同的翻译单元中。 sizeof() 只能用于完整类型。这与数组指针衰减无关。 extern float (*mvp)[4] 是一个完整的类型,它是一个指向 4 个浮点数的数组的指针。 extern float mvp[][4] 不完整,它是一个二维浮点数组,其中一个维度未指定。这是两个非常不同的东西。在任何一种情况下,mvp 都可以用作数组,只要使用正确的语法,但你只能使用sizeof,如果它实际上可以确定它的大小。

另外float mvp[][4] 是一个数组,只是它的大小是不确定的。它之所以成为数组,是因为它的内存布局像一个数组。

【讨论】:

  • extern float (*mvp)[4] 是一个指向 4 个浮点数数组的指针
  • 我已经更新了它,但原因是一样的。 sizeof 仅适用于完整类型。
【解决方案2】:

可以声明extern数组的所有维度:

extern float mvp[4][4];

这只是一个选择,让外部声明不完整,让定义担心维度。 确实很有用,因为大小不是其外部接口的一部分!如果最外层的大小从编译变为另一个,则不需要重新编译仅使用该对象的翻译单元。

为此,可能应该有一个结束数组的哨兵值/一个告诉有多少元素的变量,否则它不是很有用。


为什么不将其隐式转换为指针(只是基地址偏移量)甚至更好,为什么不抛出错误以省略第一维的大小?

它不能被转换为指针,因为声明不是一个定义。它只是说明这样的对象确实存在。该对象的定义独立于外部声明而存在。此处声明的实际对象是数组,而不是指针。

只是在数组的情况下,外部声明可以声明最外层维度,也可以省略它。


至于声称

数组在用作左值时衰减为指向其第一个元素的指针

这是完全错误的。数组表达式一个左值,当它衰减时它不再是一个左值——它保持为左值的唯一情况是作为&的操作数。

【讨论】:

  • 那为什么不将不完整的类型隐式转换成指针呢,基本上就是这样:指向数组内存区域开头的基地址。这种不完整的数组类型很奇怪,因为它的行为类似于数组类型,因为您不能递增它,但也不能检索它的大小。那么为什么不把它转换成一个指针呢?
  • @Prion:在其他可能的原因中,期望mvp 是指针的编译代码与将mvp 定义为数组的定义不兼容。
  • 我知道。因为类型不同,指向 N 个元素数组的指针是完整类型,而“不完整”数组类型则不是。不完整数组类型只是告诉某个地方存在完整数组类型的一种方式,因此必须由链接器解析。
  • @Prion 指向对象的指针,即使是大小不确定的数组也是完整类型。这是因为无论指针指向什么,指针的大小总是相同的。对于这种情况,您希望sizeof 做什么。看来您希望它变成指针或其他东西并报告错误的大小。唯一一次sizeof 可以报告数组的大小是在定义该数组的翻译单元中。
  • @Prior:此外,虽然mvp 最终会成为大多数表达式中的指针,所以mvp[i] 可以工作,&mvp 的类型会有所不同。一个是指向数组的指针,另一个是指向指针的指针。 C 语义会改变。最初,在翻译单元中可能有一个mvp[][4] 的初始声明,随后是一个解决不完整性的mvp[5][4]。如果mvp[][4] 是一个指针,这是不可能的。
猜你喜欢
  • 2021-12-20
  • 2018-09-18
  • 1970-01-01
  • 1970-01-01
  • 2011-02-02
  • 2015-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多