【问题标题】:Can't understand this way to calculate the square of a number无法理解这种计算数字平方的方法
【发布时间】:2015-03-05 21:36:40
【问题描述】:

我找到了一个计算数字平方的函数:

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)。这是为什么呢?

【问题讨论】:

  • 你有链接到你找到这些信息的地方吗?
  • int p(n)?这甚至可以编译吗?
  • 这太棒了,现在再也不用它了,改用 n*n...
  • 或更好:int q(int n) { return sizeof (char [n][n]); }
  • @ouah 假设这个问题是指codegolf.stackexchange.com/a/43262/967 我没有使用sizeof 的原因是为了保存字符。其他人:这是故意模糊的代码,是未定义的行为,@ouah 的回答是正确的。

标签: c arrays pointers c99 variable-length-array


【解决方案1】:

an int 的(变量)数组。

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

(&a)[1] 是一个指向 int 的指针,指向最后一个数组元素之后的 int。这个指针是nint&a[0]之后的元素。

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

(&a)[n]int 的一个指针,一个int 超过n 数组的最后一个数组元素。这个指针是n * nint&a[0] 之后的元素。只需减去&a[0]a 即可得到n

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

【讨论】:

  • 嗯,我明白了,但为什么在 C 语言中会发生这种情况?这背后的逻辑是什么?
  • @Emanuel 没有比指针算法对测量距离有用(通常在数组中)更严格的答案,[n] 语法声明了一个数组,并且数组分解为指针。具有这种结果的三个单独有用的东西。
  • @Emanuel 如果你问为什么有人会这样做,那么由于 UB 的性质,几乎没有理由那个行动。值得注意的是,(&a)[n]int[n] 类型,而 that 表示为 int*,因为数组表示为它们的第一个元素的地址,以防在描述。
  • 不,我不是说为什么有人会这样做,我的意思是为什么 C 标准在这种情况下会表现得这样。
  • @Emanuel Pointer Arithmetic(在本例中是该主题的一个子章节:pointer Difference)。值得在这个网站上搜索以及阅读问题和答案。它有许多有用的好处,并且在正确使用时在标准中进行了具体定义。为了完全掌握它,您必须了解您列出的代码中的类型是如何设计的。
【解决方案2】:

显然是一种 hack……但是一种不使用 * 运算符的平方数的方法(这是编码竞赛的要求)。

(&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

【讨论】:

  • 正如您明确暗示的那样,但我觉得有必要明确指出,这充其量只是一种语法技巧。乘法运算仍将在那里;只是要避免使用运算符。
  • 我知道它背后发生了什么,但我真正的问题是为什么 (&a)[k] 与 a + k * sizeof(a) / sizeof(int) 位于同一地址
  • 作为一个老程序员,当n 在编译时未知时,编译器可以将(&a) 视为指向n*sizeof(int) 对象的指针,这让我感到很吃惊。 C 曾经是一种简单 语言...
  • 这是一个非常聪明的 hack,但是你不会在生产代码中看到一些东西(希望如此)。
  • 顺便说一句,它也是UB,因为它增加了一个指针,既不指向底层数组的元素,也不指向过去。
【解决方案3】:

如果你有两个指针指向同一个数组的两个元素,那么它的差异将产生这些指针之间的元素数量。例如这段代码 sn -p 将输出 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

【讨论】:

  • 那么每个数组后面都有一个大小为n*n的矩阵?
  • @Emanuel 在这两个指针之间有一个 n x n 元素的矩阵。并且指针的差值等于 n * n,即指针之间有多少个元素。
  • 但是为什么这个大小为n*n的矩阵在后面呢?它在C中有什么用吗?我的意思是,这就像 C “分配”了更多大小为 n 的数组,而我不知道?如果是这样,我可以使用它们吗?否则,为什么会形成这个矩阵(我的意思是,它必须有它存在的目的)。
  • @Emanuel - 这个矩阵只是对在这种情况下指针算法如何工作的解释。该矩阵未分配,您不能使用它。就像已经说过几次一样,1)这段代码sn-p是一个没有实际用途的hack; 2) 你需要学习指针算法的工作原理才能理解这个技巧。
  • @Emanuel 这解释了指针算法。由于指针算法,表达式 ( &a )[n] 是指向成像二维数组的 n 元素的指针。
【解决方案4】:

要理解这个hack,首先你需要理解指针的区别,即当两个指向同一个数组元素的指针相减时会发生什么?

当一个指针从另一个指针中减去时,结果是指针之间的距离(以数组元素测量)。所以,如果p 指向a[i] 并且q 指向a[j],那么p - q 等于i - j

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

当两个指针相减时,两个指针都应该指向同一个数组对象的元素,或者是数组对象最后一个元素的后一个; 结果是两个数组元素的下标之差。 [...]。
换句话说,如果表达式PQ 分别指向数组对象的i-th 和j-th 元素,表达式(P)-(Q) 的值是@987654334 @ 提供的值适合ptrdiff_t 类型的对象。

现在我希望您知道数组名称转换为指针,a 转换为指向数组a 的第一个元素的指针。 &a 是整个内存块的地址,即数组a 的地址。下图帮助你理解(详细解释请阅读this answer):

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

所以,声明

return (&a)[n] - a; 

等价于

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

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


注意:

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

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

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

另外请注意,最好将函数p 的返回类型更改为ptrdiff_t

【讨论】:

  • “两者都应指向同一个数组对象的元素”——这对我提出了一个问题,即这个“hack”到底是不是 UB。指针算术表达式是指一个不存在的对象的假设结束:这甚至允许吗?
  • 总结一下,a是n个元素的数组的地址,所以&a[0]是这个数组的第一个元素的地址,和a一样;此外,&a[k] 将始终被视为 n 个元素的数组的地址,而与 k 无关,并且由于 &a[1..n] 也是一个向量,其元素的“位置”是连续的,这意味着第一个元素位于 x 位置,第二个位于 x + 位置(向量 a 的元素数为 n),依此类推。我对吗?另外,这是一个堆空间,是不是意味着如果我分配一个相同n个元素的新向量,它的地址就和(&a)[1]一样?
  • @Emanuel; &a[k] 是数组a 的第k 个元素的地址。 (&a)[k] 将始终被视为 k 元素数组的地址。所以,第一个元素在位置a(或&a),第二个在位置a+(数组a的元素数n)*(数组元素的大小)等等在。请注意,可变长度数组的内存是在堆栈上分配的,而不是在堆上。
  • @MartinBa; 这是否允许? 不,不允许。它的UB。查看编辑。
  • @hacks 问题的性质和你的昵称很巧合
【解决方案5】:
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

【讨论】:

    猜你喜欢
    • 2012-11-08
    • 2023-03-29
    • 1970-01-01
    • 2014-10-17
    • 2021-08-03
    • 1970-01-01
    • 2018-05-24
    • 2017-09-28
    • 1970-01-01
    相关资源
    最近更新 更多