无法理解这种计算数字平方的方法


135

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

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

它返回值n 2。问题是,它是如何做到的?经过一些测试,我发现介于(&a)[k]和之间(&a)[k+1]sizeof(a)/ sizeof(int)。这是为什么?


6
您是否有指向找到此信息的链接?
R Sahu 2015年

4
int p(n)?可以编译吗?
barak manos

78
这太棒了,现在不再使用它,而使用n * n代替...

26
或更好:int q(int n) { return sizeof (char [n][n]); }
哇,2015年

17
@ouah假设此问题指向codegolf.stackexchange.com/a/43262/967,我未使用的原因sizeof是保存字符。其他人:这是故意含糊的代码,它是未定义的行为,@ ouah的答案是正确的。
ecatmur'1

Answers:


117

显然是骇客……但是一种不使用*运算符就对数字进行平方的方法(这是编码竞赛的要求)。

(&a)[n] 

等效于指向intlocation 的指针

(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

11
正如您明确暗示的那样,但我觉得有必要明确说明,这充其量是语法黑客。乘法运算仍将在该处;只是避免了操作员。
汤米

我知道发生了什么,但是我真正的问题是为什么(&a)[k]与a + k * sizeof(a)/ sizeof(int)位于同一地址
Emanuel 2015年

33
作为一个古老的编码器,我对编译器可以将其(&a)视为指向n*sizeof(int)何时n编译时未知的对象的指针感到惊讶。C曾经是一种简单的语言……
Floris 2015年

这是一个非常聪明的hack,但是(希望)您不会在生产代码中看到这种东西。
John Odom 2015年

14
顺便说一句,它也是UB,因为它增加了一个指针,既不指向基础数组的元素,也不指向过去。
Deduplicator 2015年

86

要了解这种技巧,首先需要了解指针的区别,即,减去两个指向同一数组元素的指针时会发生什么?

当一个指针与另一个指针相减时,结果就是指针之间的距离(以数组元素为单位)。因此,如果p指向a[i]q指向a[j],则p - q等于i - j

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

当减去两个指针时,两个指针都将指向同一数组对象的元素,或者指向数组对象的最后一个元素;结果是两个数组元素的下标不同。[...]。
换句话说,如果表达式PQ分别指向数组对象的i-th和j-th元素,则表达式(P)-(Q)具有值,i−j只要该值适合于type对象ptrdiff_t

现在,我希望您知道将数组名转换为指针,a并将其转换为指向array的第一个元素的指针a&a是整个内存块的地址,即它是array的地址a。下图将帮助您理解(请阅读此答案以获取详细说明):

在此处输入图片说明

这将帮助您理解 i 数组的原因a&a地址,以及(&a)[i] i 数组(与的大小相同)的地址如何a

所以,声明

return (&a)[n] - a; 

相当于

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

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


注意:

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

当减去两个指针时,两个指针均应指向同一数组对象的元素,或者指向数组对象的最后一个元素;结果是两个数组元素的下标不同。结果的大小是实现定义的,并且其类型(有符号整数类型)ptrdiff_t<stddef.h>标头中定义。如果结果无法在该类型的对象中表示,则该行为未定义。

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

另请注意,最好将函数的返回类型更改pptrdiff_t


“两者均应指向同一数组对象的元素”-这对我提出了一个问题,即这个“ hack”是否毕竟不是UB。指针算术表达式是指不存在的对象的假设端:甚至允许这样做吗?
2015年

总而言之,a是n个元素的数组的地址,因此&a [0]是该数组中第一个元素的地址,与a相同;另外,&a [k]将始终被视为n个元素的数组的地址,而与k无关,并且由于&a [1..n]也是一个向量,因此其元素的“位置”是连续的,这意味着第一个元素位于位置x,第二个元素位于位置x +(向量a的元素数量为n),依此类推。我对吗?另外,这是一个堆空间,这是否意味着如果我分配相同n个元素的新向量,则其地址与(&a)[1]相同?
伊曼纽尔2015年

1
@伊曼纽尔; &a[k]karray 的第th个元素的地址a。它将(&a)[k]始终被视为k元素数组的地址。因此,第一个元素位于a(或&a)位置,第二个a元素位于+(数组的元素数an)*(数组元素的大小)等位置。请注意,可变长度数组的内存分配在堆栈上,而不是堆上。
haccks

@MartinBa; 甚至允许吗?不可以。它的UB。参见编辑。

1
@haccks问题的性质和您的昵称之间的巧合
Dimitar Tsonev

35

a是的(变量)数组n int

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

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

(&a)[2]是一个指针int之一int过去的两个阵列的最后一个数组元素。该指针是2 * n int之后的元素&a[0]

(&a)[n]是一个指针int一个int过去的最后一个数组元素n的数组。该指针是n * n int之后的元素&a[0]。只要减去&a[0]或就a可以了n

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


好吧,我明白了,但是为什么这会在C语言中发生?这背后的逻辑是什么?
伊曼纽尔

@Emanuel对此的回答没有比严格的答案更精确的了,因为指针算术对于测量距离(通常在数组中)很有用,[n]语法声明一个数组并且数组分解为指针。结果产生了三个分别有用的东西。
汤米

1
@Emanuel,如果您问为什么有人会这样做,则没有什么理由,并且每种理由来由于行动UB性质。值得注意的(&a)[n]是type int[n]表示为int*由于数组表示为其第一个元素的地址,以防在描述中不清楚。
WhozCraig 2015年

不,我不是说为什么有人会这样做,而是想为什么C标准在这种情况下会表现得如此。
伊曼纽尔2015年

1
@Emanuel 指针算术(在本例中为该主题的子章节:指针差异)。值得谷歌搜索以及在此站点上阅读问题和答案。它具有许多有用的好处,并且在正确使用时会在标准中进行具体定义。为了完全掌握它,您必须了解如何编写列出的代码中的类型
WhozCraig 2015年

12

如果您有两个指向同一数组中两个元素的指针,则其区别将产生这些指针之间的元素数量。例如,此代码段将输出2。

int a[10];

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

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

现在考虑表达

(&a)[n] - a;

在此表达式中a具有type int *并指向其第一个元素。

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

( &a )[n]

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

因此在(&a)[n]和之间 a有n行n个元素。因此,差将等于n * n


那么在每个数组后面都有一个大小为n * n的矩阵?
伊曼纽尔

@Emanuel在这两个指针之间有一个nxn元素矩阵。指针的差值等于n * n,即指针之间有多少个元素。
来自莫斯科的弗拉德(Vlad)

但是为什么这个大小为n * n的矩阵后面呢?它在C中有什么用吗?我的意思是,这就像C“分配”了n个大小更多的数组,而我不知道吗?如果可以,我可以使用它们吗?否则,为什么要形成这个矩阵(我的意思是,它必须有一个存在的目的)。
伊曼纽尔

2
@Emanuel-此矩阵只是在这种情况下指针算法如何工作的说明。此矩阵未分配,您不能使用它。就像已经说过几次一样:1)此代码段是没有实际用途的hack;2)您需要学习指针算法的工作原理才能理解这种技巧。
void_ptr 2015年

@Emanuel这说明了指针算法。由于指针算术运算,展开(&a)[n]是指向成像的二维数组的n元素的指针。
弗拉德(Vlad),来自莫斯科

4
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
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.