这段代码如何在不使用sizeof()的情况下确定数组大小?


134

通过一些C面试问题,我找到了一个问题,指出“如何在不使用sizeof运算符的情况下在C中查找数组的大小?”,并提供以下解决方案。它有效,但是我不明白为什么。

#include <stdio.h>

int main() {
    int a[] = {100, 200, 300, 400, 500};
    int size = 0;

    size = *(&a + 1) - a;
    printf("%d\n", size);

    return 0;
}

如预期的那样,它返回5。

编辑:人们指出了这个答案,但是语法确实有所不同,即索引方法

size = (&arr)[1] - arr;

因此,我认为这两个问题都是有效的,并且对问题的处理方法略有不同。谢谢大家的大力帮助和详尽的解释!


13
好吧,找不到它,但严格来说看起来确实如此。附件J.2明确指出:一元*运算符的操作数具有无效值是未定义的行为。这里&a + 1没有指向任何有效的对象,因此无效。
尤金(Eugene Sh)。



@AlmaDo好,语法确实有所不同,即索引部分,所以我认为这个问题本身仍然有效,但是我可能错了。感谢您指出!
janojlic

1
@janojlicz它们基本相同,因为(ptr)[x]与相同*((ptr) + x)
SS Anne

Answers:


135

将1加到指针时,结果是指向类型的对象(即数组)对象序列中下一个对象的位置。如果p指向一个int对象,p + 1则将指向int序列中的下一个对象。如果p指向的5元素数组int(在这种情况下为&a),p + 1则将指向序列中的下一个5元素数组int

减去两个指针(假设它们都指向同一个数组对象,或者一个指针指向数组的最后一个元素),则得出这两个指针之间的对象(数组元素)的数量。

该表达式&a产生的地址a,并具有类型int (*)[5](指向的5元素数组的指针int)。表达&a + 1产生的下一个5个元素的数组的地址int以下a,并且还具有的类型int (*)[5]。该表达式*(&a + 1)取消引用的结果&a + 1,从而产生int后一个元素的第一个地址a,并具有type int [5],在这种情况下,它会“衰减”为type的表达式int *

类似地,表达式a“衰减”为指向数组第一个元素的指针,并具有type int *

图片可能会有所帮助:

int [5]  int (*)[5]     int      int *

+---+                   +---+
|   | <- &a             |   | <- a
| - |                   +---+
|   |                   |   | <- a + 1
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+
|   | <- &a + 1         |   | <- *(&a + 1)
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
| - |                   +---+
|   |                   |   |
+---+                   +---+

这是同一存储的两个视图-在左侧,我们将其视为5个元素的数组序列int,而在右侧,我们将其视为的序列int。我还展示了各种表达式及其类型。

请注意,该表达式会*(&a + 1)导致未定义的行为

...
如果结果指向数组对象的最后一个元素,则不应将其用作被评估的一元*运算符的操作数。

C 2011在线草案,6.5.6 / 9


13
该“不得使用”文本为官方文本:C 2018 6.5.6 8.
Eric Postpischil,

@EricPostpischil:您是否具有2018年发布前草稿的链接(类似于N1570.pdf)?
约翰·博德

1
@JohnBode:此答案具有指向Wayback Machine的链接。我在购买的副本中检查了官方标准。
埃里克·波斯特皮希尔

7
因此,如果编写size = (int*)(&a + 1) - a;此代码将是完全有效的?:o
Gizmo

@Gizmo他们最初可能不是这样写的,因为那样就必须指定元素类型。原始文档可能被定义为在不同元素类型上用于类型通用的宏。
卢申科

35

这条线是最重要的:

size = *(&a + 1) - a;

如您所见,它首先获取地址a并添加一个。然后,它取消引用该指针并a从中减去其原始值。

C语言中的指针算术使它返回数组或中元素的数量5。加1和&a是指向int之后5 秒的下一个数组的指针a。之后,此代码取消对结果指针的引用a,并从中减去(已衰减为指针的数组类型),从而得出数组中的元素数。

有关指针算法工作原理的详细信息:

假设你有一个指针xyz,它指向的int类型和包含的价值(int *)160。当从中减去任何数字时xyz,C表示从中减去的实际数量xyz是该数字乘以它所指向的类型的大小。例如,如果5从中减去xyz,则xyz结果的值将是xyz - (sizeof(*xyz) * 5)指针算术不适用的情况。

由于a是阵列5 int类型,得到的值将是5。然而,这与一个指针,只以与阵列将无法工作。如果您使用指针尝试此操作,结果将始终为1

这是一个小示例,显示了地址以及地址的不确定性。左侧显示地址:

a + 0 | [a[0]] | &a points to this
a + 1 | [a[1]]
a + 2 | [a[2]]
a + 3 | [a[3]]
a + 4 | [a[4]] | end of array
a + 5 | [a[5]] | &a+1 points to this; accessing past array when dereferenced

这意味着代码a&a[5](或a+5)中减去,得到5

请注意,这是未定义的行为,在任何情况下都不应使用。不要期望此行为在所有平台上都一致,也不要在生产程序中使用它。


27

嗯,我怀疑这在C的早期是无法实现的。但是它很聪明。

一次执行一个步骤:

  • &a 获取指向int [5]类型的对象的指针
  • +1 假设存在这些对象的数组,则获取下一个此类对象
  • * 有效地将该地址转换为指向int的类型指针
  • -a 减去两个int指针,返回它们之间的int实例计数。

考虑到正在进行的某些类型操作,我不确定这是否完全合法(在这里我是指语言律师合法-不会在实践中起作用)。例如,当两个指针指向同一数组中的元素时,只允许它们相减。*(&a+1)是通过访问另一个数组(虽然是父数组)来合成的,因此实际上不是指向与相同数组的指针a。同样,虽然允许您合成指针到数组的最后一个元素之后,并且您可以将任何对象都视为1个元素的数组,但是*在此合成指针上“允许” 取消引用()操作,即使它在这种情况下没有任何行为!

我怀疑在C的早期(K&R语法,有人吗?),数组*(&a+1)会更快地衰减为指针,因此,它们可能只返回int **类型的下一个指针的地址。现代C ++的更严格的定义肯定允许存在指向数组类型的指针并知道数组的大小,并且可能C语言标准也紧随其后。所有C函数代码仅将指针作为参数,因此技术上的可见差异很小。但是我只是在这里猜测。

这种详细的合法性问题通常适用于C解释器或lint类型的工具,而不是编译后的代码。解释器可能将2D数组实现为指向数组的指针的数组,因为要实现的功能要少一些,在这种情况下,对+1的取消引用将是致命的,即使执行+1也会给出错误的答案。

另一个可能的弱点可能是C编译器可能会对齐外部数组。想象一下,如果这是一个5个字符的数组(char arr[5]),则在程序执行&a+1时会调用“数组数组”行为。编译器可能会决定char arr[][5]实际生成5个字符()的数组作为8个字符(char arr[][8])的数组,以便外部数组很好地对齐。我们正在讨论的代码现在将报告数组大小为8,而不是5。我并不是说特定的编译器肯定会这样做,但是可以。


很公平。但是出于难以解释的原因,每个人都使用sizeof()/ sizeof()吗?
宝石泰勒,

5
大多数人都这样做。例如,sizeof(array)/sizeof(array[0])给出数组中元素的数量。
SS安妮

允许C编译器对齐数组,但是我不认为可以在这样做后更改数组的类型。对齐将通过插入填充字节来更实际地实现。
凯文

1
指针的减法不仅限于将两个指针添加到同一数组中,还允许指针比数组末尾多一个。&a+1被定义为。正如约翰·博林格(John Bollinger)指出的,*(&a+1)不是,因为它试图取消引用不存在的对象。
埃里克·波斯特皮希尔

5
编译器无法实现char [][5]as char arr[][8]。数组只是其中的重复对象。没有填充。此外,这将破坏C 2018 6.5.3.4 7中的(非标准)示例2,该示例告诉我们可以使用来计算数组中的元素数量sizeof array / sizeof array[0]
埃里克·波斯特皮希尔
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.