这段代码如何在不使用 sizeof( ) 的情况下确定数组大小?
How does this piece of code determine array size without using sizeof( )?
通过一些 C 面试问题,我发现了一个说明 "How to find the size of an array in C without using the sizeof operator?" 的问题,其解决方案如下。它有效,但我不明白为什么。
#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;
}
不出所料,returns 5.
编辑:人们指出 this 答案,但语法确实有点不同,即索引方法
size = (&arr)[1] - arr;
所以我相信这两个问题都是有效的,并且解决问题的方法略有不同。感谢大家的大力帮助和详尽的解释!
这一行最重要:
size = *(&a + 1) - a;
如您所见,它首先获取 a
的地址并对其加一。然后,它取消引用该指针并从中减去 a
的原始值。
C 中的指针运算导致 return 数组中的元素数,或 5
。和&a
相加是指向a
后5int
s的下一个数组的指针。之后,此代码取消引用结果指针并从中减去 a
(已衰减为指针的数组类型),给出数组中元素的数量。
关于指针算法如何工作的详细信息:
假设您有一个指向 int
类型并包含值 (int *)160
的指针 xyz
。当您从 xyz
中减去任何数字时,C 指定从 xyz
中减去的实际数量是该数字乘以它指向的类型的大小。例如,如果您从 xyz
中减去 5
,如果指针算法不适用,则结果 xyz
的值将是 xyz - (sizeof(*xyz) * 5)
。
由于a
是5
int
类型的数组,因此结果值为5。但是,这不适用于指针,只能用于数组。如果你用一个指针试试这个,结果总是 1
.
这里有一个小例子,显示了地址以及它是如何未定义的。 left-hand 方显示地址:
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 指针,return计算它们之间的 int 实例数。
我不确定它是否完全合法(在此我的意思是 language-lawyer 合法 - 它在实践中不会起作用),因为某些类型操作正在进行。例如,当两个指针指向同一个数组中的元素时,您只需 "allowed" 减去两个指针。 *(&a+1)
是通过访问另一个数组合成的,尽管它是父数组,因此实际上并不是指向与 a
相同数组的指针。
此外,虽然您可以合成一个超过数组最后一个元素的指针,并且您可以将任何对象视为 1 个元素的数组,但取消引用 (*
) 的操作不是 "allowed"这个合成指针,即使它在这种情况下没有任何行为!
我怀疑在 C 的早期(K&R 语法,有人知道吗?),数组更快地变成指针,所以 *(&a+1)
可能只是 return 的地址int** 类型的下一个指针。现代 C++ 更严格的定义肯定允许指向数组类型的指针存在并知道数组大小,并且 C 标准可能已经效仿。所有 C 函数代码仅将指针作为参数,因此技术上的可见差异很小。不过我这里只是猜测。
这种详细的合法性问题通常适用于C解释器或lint类型的工具,而不是编译后的代码。解释器可能将二维数组实现为指向数组的指针数组,因为要实现的运行时功能较少,在这种情况下取消引用 +1 将是致命的,即使它有效也会给出错误的答案。
另一个可能的弱点是 C 编译器可能对齐外部数组。想象一下,如果这是一个包含 5 个字符的数组 (char arr[5]
),当程序执行 &a+1
时,它会调用 "array of array" 行为。编译器可能会决定将 5 个字符的数组 (char arr[][5]
) 实际上生成为 8 个字符的数组 (char arr[][8]
),以便外部数组很好地对齐。我们正在讨论的代码现在会将数组大小报告为 8,而不是 5。我并不是说特定的编译器肯定会这样做,但它可能会这样做。
当您将 1 加到指针时,结果是 pointed-to 类型的对象序列(即数组)中下一个对象的位置。如果 p
指向一个 int
对象,那么 p + 1
将指向序列中的下一个 int
。如果 p
指向 int
的 5 元素数组(在本例中,表达式 &a
),则 p + 1
将指向下一个 5 - 序列中 int
的元素数组。
两个指针相减(前提是它们都指向同一个数组对象,或者一个指针指向数组的最后一个元素)得到这两个指针之间的对象(数组元素)的数量。
表达式 &a
产生 a
的地址,类型为 int (*)[5]
(指向 int
的 5 元素数组的指针)。表达式 &a + 1
生成 a
之后 int
的下一个 5 元素数组的地址,并且类型也为 int (*)[5]
。表达式 *(&a + 1)
取消引用 &a + 1
的结果,因此它产生 a
最后一个元素之后的第一个 int
的地址,类型为 int [5]
,在此上下文中“衰减”为 int *
.
类型的表达式
类似地,表达式 a
“衰减”为指向数组第一个元素的指针,类型为 int *
.
图片可能有帮助:
int [5] int (*)[5] int int *
+---+ +---+
| | <- &a | | <- a
| - | +---+
| | | | <- a + 1
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
+---+ +---+
| | <- &a + 1 | | <- *(&a + 1)
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
+---+ +---+
这是同一存储的两个视图 - 在左侧,我们将其视为 int
的 5 元素数组序列,而在右侧,我们将其视为int
的序列。我还展示了各种表达式及其类型。
注意,表达式 *(&a + 1)
导致 未定义的行为:
...
If the result points one past the last element of the array object, it
shall not be used as the operand of a unary * operator that is evaluated.
C 2011 Online Draft, 6.5.6/9
通过一些 C 面试问题,我发现了一个说明 "How to find the size of an array in C without using the sizeof operator?" 的问题,其解决方案如下。它有效,但我不明白为什么。
#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;
}
不出所料,returns 5.
编辑:人们指出 this 答案,但语法确实有点不同,即索引方法
size = (&arr)[1] - arr;
所以我相信这两个问题都是有效的,并且解决问题的方法略有不同。感谢大家的大力帮助和详尽的解释!
这一行最重要:
size = *(&a + 1) - a;
如您所见,它首先获取 a
的地址并对其加一。然后,它取消引用该指针并从中减去 a
的原始值。
C 中的指针运算导致 return 数组中的元素数,或 5
。和&a
相加是指向a
后5int
s的下一个数组的指针。之后,此代码取消引用结果指针并从中减去 a
(已衰减为指针的数组类型),给出数组中元素的数量。
关于指针算法如何工作的详细信息:
假设您有一个指向 int
类型并包含值 (int *)160
的指针 xyz
。当您从 xyz
中减去任何数字时,C 指定从 xyz
中减去的实际数量是该数字乘以它指向的类型的大小。例如,如果您从 xyz
中减去 5
,如果指针算法不适用,则结果 xyz
的值将是 xyz - (sizeof(*xyz) * 5)
。
由于a
是5
int
类型的数组,因此结果值为5。但是,这不适用于指针,只能用于数组。如果你用一个指针试试这个,结果总是 1
.
这里有一个小例子,显示了地址以及它是如何未定义的。 left-hand 方显示地址:
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 指针,return计算它们之间的 int 实例数。
我不确定它是否完全合法(在此我的意思是 language-lawyer 合法 - 它在实践中不会起作用),因为某些类型操作正在进行。例如,当两个指针指向同一个数组中的元素时,您只需 "allowed" 减去两个指针。 *(&a+1)
是通过访问另一个数组合成的,尽管它是父数组,因此实际上并不是指向与 a
相同数组的指针。
此外,虽然您可以合成一个超过数组最后一个元素的指针,并且您可以将任何对象视为 1 个元素的数组,但取消引用 (*
) 的操作不是 "allowed"这个合成指针,即使它在这种情况下没有任何行为!
我怀疑在 C 的早期(K&R 语法,有人知道吗?),数组更快地变成指针,所以 *(&a+1)
可能只是 return 的地址int** 类型的下一个指针。现代 C++ 更严格的定义肯定允许指向数组类型的指针存在并知道数组大小,并且 C 标准可能已经效仿。所有 C 函数代码仅将指针作为参数,因此技术上的可见差异很小。不过我这里只是猜测。
这种详细的合法性问题通常适用于C解释器或lint类型的工具,而不是编译后的代码。解释器可能将二维数组实现为指向数组的指针数组,因为要实现的运行时功能较少,在这种情况下取消引用 +1 将是致命的,即使它有效也会给出错误的答案。
另一个可能的弱点是 C 编译器可能对齐外部数组。想象一下,如果这是一个包含 5 个字符的数组 (char arr[5]
),当程序执行 &a+1
时,它会调用 "array of array" 行为。编译器可能会决定将 5 个字符的数组 (char arr[][5]
) 实际上生成为 8 个字符的数组 (char arr[][8]
),以便外部数组很好地对齐。我们正在讨论的代码现在会将数组大小报告为 8,而不是 5。我并不是说特定的编译器肯定会这样做,但它可能会这样做。
当您将 1 加到指针时,结果是 pointed-to 类型的对象序列(即数组)中下一个对象的位置。如果 p
指向一个 int
对象,那么 p + 1
将指向序列中的下一个 int
。如果 p
指向 int
的 5 元素数组(在本例中,表达式 &a
),则 p + 1
将指向下一个 5 - 序列中 int
的元素数组。
两个指针相减(前提是它们都指向同一个数组对象,或者一个指针指向数组的最后一个元素)得到这两个指针之间的对象(数组元素)的数量。
表达式 &a
产生 a
的地址,类型为 int (*)[5]
(指向 int
的 5 元素数组的指针)。表达式 &a + 1
生成 a
之后 int
的下一个 5 元素数组的地址,并且类型也为 int (*)[5]
。表达式 *(&a + 1)
取消引用 &a + 1
的结果,因此它产生 a
最后一个元素之后的第一个 int
的地址,类型为 int [5]
,在此上下文中“衰减”为 int *
.
类似地,表达式 a
“衰减”为指向数组第一个元素的指针,类型为 int *
.
图片可能有帮助:
int [5] int (*)[5] int int *
+---+ +---+
| | <- &a | | <- a
| - | +---+
| | | | <- a + 1
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
+---+ +---+
| | <- &a + 1 | | <- *(&a + 1)
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
| - | +---+
| | | |
+---+ +---+
这是同一存储的两个视图 - 在左侧,我们将其视为 int
的 5 元素数组序列,而在右侧,我们将其视为int
的序列。我还展示了各种表达式及其类型。
注意,表达式 *(&a + 1)
导致 未定义的行为:
...
If the result points one past the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.
C 2011 Online Draft, 6.5.6/9