[英]How does this piece of code determine array size without using sizeof( )?
通过一些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;
所以我相信这两个问题都是有效的,并且对问题的解决方法略有不同。 谢谢大家的大力帮助和彻底的解释!
向指针添加1时,结果是指向类型的对象序列(即数组)中下一个对象的位置。 如果p
指向int
对象,则p + 1
将指向序列中的下一个int
。 如果p
指向int
的5元素数组(在本例中为表达式&a
),那么p + 1
将指向序列中int
的下一个5元素数组 。
减去两个指针(前提是它们都指向同一个数组对象,或者一个指向一个超过数组的最后一个元素)会产生这两个指针之间的对象数(数组元素)。
表达&a
产生的地址a
,并且具有类型int (*)[5]
指针至5个元素的数组的int
)。 表达&a + 1
产生的下一5个元素的数组的地址int
以下a
,并且还具有的类型int (*)[5]
表达式*(&a + 1)
解引用的结果&a + 1
,使得其产生所述第一地址int
的最后一个元素以下a
,并且具有类型int [5]
在此上下文中,“衰减”到int *
表达式。
类似地,表达a
“衰减”的指针数组的第一元素和具有类型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
这条线最重要:
size = *(&a + 1) - a;
正如你所看到的,它首先采取的地址a
,并增加了一个它。 然后,取消引用该指针和减去的原始值a
从它。
C中的指针运算会导致返回数组中元素的数量,或者5
。 加入一种和&a
是指向的5下一个阵列int
年代后a
。 之后,此代码取消引用结果指针并从中减去a
(已衰减为指针的数组类型),从而给出数组中的元素数。
指针算术如何工作的详细信息:
假设你有一个指向xyz
的指针,它指向一个int
类型并包含值(int *)160
。 当你从xyz
减去任何数字时,C指定从xyz
减去的实际数量是该数字乘以它指向的类型的数量。 例如,如果从xyz
减去5
,则如果指针算术不适用,则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[5]
(或a+5
)中减去a
,给出5
。
请注意,这是未定义的行为,不应在任何情况下使用。 不要指望这种行为在所有平台上都是一致的,并且不要在生产程序中使用它。
嗯,我怀疑这是在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将是致命的,即使它工作也会给出错误的答案。
另一个可能的弱点可能是C编译器可能对齐外部数组。 想象一下,如果这是一个包含5个字符的数组( char arr[5]
),当程序执行&a+1
它会调用“数组数组”行为。 编译器可能会认为5个字符数组的数组( char arr[][5]
)实际上是作为8个字符数组( char arr[][8]
)的数组生成的,因此外部数组很好地对齐。 我们正在讨论的代码现在将数组大小报告为8,而不是5.我不是说特定的编译器肯定会这样做,但它可能会。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.