通过一些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。
编辑:人们指出了this的答案,但是语法确实有所不同,即索引方法
size = (&arr)[1] - arr;
因此,我认为这两个问题都是有效的,并且对该问题的解决方法略有不同。谢谢大家的大力帮助和详尽的解释!
答案 0 :(得分:91)
在指针上加1时,结果是下一个对象在指向类型的对象序列(即数组)中的位置。如果p
指向int
对象,则p + 1
将指向序列中的下一个int
。如果p
指向int
的5元素数组(在这种情况下为表达式&a
),则p + 1
将指向下一个 5元素int
序列中的数组。
减去两个指针(假设它们都指向同一个数组对象,或者一个指针指向数组的最后一个元素),则得出这两个指针之间的对象(数组元素)的数量。
表达式&a
产生a
的地址,并具有类型int (*)[5]
(指向int
的5元素数组的指针)。表达式&a + 1
产生int
之后的a
的下一个5元素数组的地址,并且类型为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)
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
+---+ +---+
这是同一存储的两个视图-在左侧,我们将其作为int
的5元素数组的序列查看,而在右侧,我们将其视为一系列的{ int
。我还展示了各种表达式及其类型。
请注意,表达式*(&a + 1)
导致未定义的行为:
...
如果结果指向数组对象的最后一个元素之后,它将 不得用作被评估的一元*运算符的操作数。
C 2011 Online Draft,6.5.6 / 9
答案 1 :(得分:28)
此行最重要:
size = *(&a + 1) - a;
如您所见,它首先获取a
的地址并添加一个地址。然后,它取消引用该指针,并从中减去a
的原始值。
C中的指针算术运算符使它返回数组或5
的大小。加一个和&a
本质上是指向a
之后的下一个字节的指针。之后,此代码取消对结果指针的引用,并从中减去a
(衰减为指针的数组类型),得到数组的大小。
有关指针算术如何工作的详细信息:
假设您有一个指针xyz
,它指向一种int
类型,并包含值160
。当从xyz
中减去任何数字时,C指定从xyz
中减去的实际数量是该数字乘以它指向的类型的大小。例如,如果您从5
中减去xyz
,则所得的xyz
的值为xyz - sizeof(*xyz) * 5
,而不考虑指针算术。
由于a
是5
int
类型的数组,因此结果值为5。但是,这不适用于指针,仅适用于数组。如果您使用指针尝试此操作,结果将始终为1
。
请注意,这是未定义的行为,在任何情况下都不应使用。不要期望此行为在所有平台上都一致,并且不要在生产程序中使用它。
答案 2 :(得分:23)
嗯,我怀疑这在C的早期是无法解决的。但是它很聪明。
一次执行一个步骤:
// ...
async getTaskInfo() {
this.taskId = this.navParams.get('taskId');
// If you want to use await, getTaskById() should return a promise
this.task = await this.api.getTaskById(this.taskId).toPromise();
// I'm assuming the task has an userId property
if(this.task && this.task.userId) {
// Update the value of the control
this.taskForm.get('assignedUserId').setValue(this.task.userId);
}
}
// ...
获得一个指向int [5]类型的对象的指针&a
假设存在下一个对象,则获取下一个此类对象+1
有效地将该地址转换为指向int类型的指针*
减去两个int指针,返回它们之间的int实例数。鉴于某些类型的操作正在进行中,我不确定这是完全合法的(在这里我指的是语言律师的合法性-在实践中不可行)。例如,当两个指针指向同一数组中的元素时,只允许它们相减。 -a
是通过访问另一个数组(虽然是父数组)来合成的,因此实际上并没有指向与*(&a+1)
相同的数组的指针。
另外,虽然允许合成指针到数组的最后一个元素之后,并且您可以将任何对象视为1个元素的数组,但是在此合成对象上“不允许”进行解引用(a
)操作指针,即使在这种情况下也没有任何作用!
我怀疑在C的早期(K&R语法,有人吗?),数组以更快的速度衰减为指针,所以*
可能只返回int *类型的下一个指针的地址。 *。现代C ++的更严格的定义肯定允许存在指向数组类型的指针并知道数组的大小,并且可能C语言标准也紧随其后。所有C函数代码仅将指针作为参数,因此技术上的可见差异很小。但我只是在这里猜测。
这类详细的合法性问题通常适用于C解释器或lint类型的工具,而不适用于编译后的代码。解释器可能将2D数组实现为指向数组的指针的数组,因为要实现的功能要少一些,在这种情况下,取消对+1的操作将是致命的,即使执行+1也会给出错误的答案。
另一个可能的弱点可能是C编译器可能会对齐外部数组。想象一下,如果这是一个5个字符的数组(*(&a+1)
),则当程序执行char arr[5]
时,它正在调用“ array of array”行为。编译器可能会决定实际上将5个字符(&a+1
)的数组生成为8个字符(char arr[][5]
)的数组,以便外部数组很好地对齐。我们正在讨论的代码现在将报告数组大小为8,而不是5。我并不是说特定的编译器肯定会这样做,但是可以。