无法理解这样计算一个数字的平方

我发现一个函数可以计算一个数字的平方:

int p(int n) {
int a[n]; //works on C99 and above
return (&a)[n] - a;
}

它返回 n2的值。问题是,它是怎么做到的?经过一些测试,我发现 (&a)[k](&a)[k+1]之间是 sizeof(a)/sizeof(int)。为什么?

9401 次浏览

an int的(变量)数组。

&a是指向 n int的(变量)数组的指针。

(&a)[1]是一个指针 int一个 int过去的最后一个数组元素。这个指针是 n int元素在 &a[0]之后。

(&a)[2]是一个指针,指向 intint之间两个数组的最后一个数组元素。这个指针是 2 * n int元素在 &a[0]之后。

(&a)[n]是指向 intint的指针,指向 n阵列的最后一个数组元素。此指针是 &a[0]后面的 n * n int元素。只要减去 &a[0]或者 a你就得到了 n

当然,这在技术上是没有未定义行为的,即使它在你的机器上工作,因为 (&a)[n]不指向数组内部,也不指向最后一个数组元素(这是指针算法的 C 规则所要求的)。

显然,这是一种黑客技术... ... 但是这是一种不使用 *操作符而平方一个数字的方法(这是一个编码竞赛的要求)。

(&a)[n]

相当于在位置上指向 int的指针

(a + sizeof(a[n])*n)

因此整个表达式是

  (&a)[n] -a


= (a + sizeof(a[n])*n -a) /sizeof(int)


= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n

如果您有两个指向同一数组中两个元素的指针,那么它们之间的差异将产生这些指针之间的元素数。例如,此代码段将输出2。

int a[10];


int *p1 = &a[1];
int *p2 = &a[3];


printf( "%d\n", p2 - p1 );

现在让我们考虑表达

(&a)[n] - a;

在这个表达式中,a具有 int *类型并指向它的第一个元素。

表达式 &a具有 int ( * )[n]类型并指向成像二维数组的第一行。它的值与 a的值相匹配,尽管类型不同。

( &a )[n]

是这个成像二维数组的第 n 个元素,类型为 int[n],这是成像数组的第 n 行。在表达式 (&a)[n] - a中,它被转换为其第一个元素的地址,并且类型为‘ int * 。

所以在 (&a)[n]a之间有 n 行 n 个元素。所以差等于 n * n

要理解这种破解,首先需要理解指针的差异,即减去指向 同一个数组元素的两个指针会发生什么?

当一个指针减去另一个指针时,结果是指针之间的距离(以数组元素表示)。因此,如果 p指向 a[i]q指向 a[j],那么 ABC4等于 i - j

C11:6.5.6加法运算符(p9) :

当减去两个指针 时,两个指针都将指向同一数组对象的元素,或者指向数组对象的最后一个元素 结果是两个数组元素下标的差异之后的一个元素。[...].
换句话说,如果表达式 PQ分别指向数组对象的 i-th 和 j-th 元素,则 表达式 ABC4的值为 i−j提供的值适合于 ptrdiff_t类型的对象。

现在我希望你知道数组名转换为指针,a转换为指针到数组 a的第一个元素。&a是整个内存块的地址,也就是数组 a的地址。下面的图表将帮助你理解(请阅读本答案以获得详细解释) :

enter image description here

这将帮助您理解为什么 a&a具有相同的地址,以及 (&a)[i]如何是 i这个数组的地址(大小与 a相同)。

那么,声明

return (&a)[n] - a;

相当于

return (&a)[n] - (&a)[0];

这个差将给出指针 (&a)[n](&a)[0]之间的元素数,它们是 n int元素中的每一个 n数组。因此,总数组元素是 n*n = n2


注意:

C11:6.5.6加法运算符(p9) :

当减去两个指针时,< strong > 两个都指向同一数组对象的元素, 或数组对象 的最后一个元素后面的一个; 结果是 两个数组元素的下标, 其类型(有符号整数类型)是在 <stddef.h>头中定义的 ptrdiff_t。 如果结果在该类型的对象中不可表示,则该行为是未定义的。

由于 (&a)[n]既不指向同一数组对象的元素,也不指向数组对象的最后一个元素之后的元素,因此 (&a)[n] - a将调用 未定义行为

还要注意,最好将函数 p的返回类型改为 ptrdiff_t

Expression     | Value                | Explanation
a              | a                    | point to array of int elements
a[n]           | a + n*sizeof(int)    | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a             | a                    | point to array of (n int elements array)
(&a)[n]        | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int)      | int[n] is a type of n-int-element array

因此,

  1. (&a)[n]的类型是 int[n]指针
  2. a的类型是 int指针

现在,表达式 (&a)[n]-a执行指针减法:

  (&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n