不使用 * operator hack-y 方式获取产品

Get Product without using * operator hack-y way

有人可以帮我理解以下逻辑如何解决获取 ab 的乘积吗?

int getProd(int a, int b){
    return (uintptr_t)&((char (*) [a])0x0)[b];
}

假设我们有一个指针 p,它指向大小为 a 的对象。

如果我们然后说 p + b,我们要求指向第 b 个对象,指向 p 点之后的指针。

因此实际的新指针值(无论如何在字节寻址的机器上)将按 a 缩放,即指向对象的大小。也就是说,“在幕后”,编译器将做一些更像 p + b * a.

的事情

所以我们可以看到乘法 a * b 正在发生——但随后它被添加到 p 的原始值。

因此,如果我们使用初始值 0,我们将只得到 a * b。这就是 hacky getProd 函数所做的。

让我们分解一下:

                           0x0

值 0,在指针上下文中也称为 空指针。 [脚注:这个定义比较复杂,但我们暂时不用担心。]

              char (*) [a]

这是一种类型:“指向 char 大小 a 数组的指针。

             (char (*) [a])0x0

这是一个强制转换:获取空指针,将其强制转换为“指向 char 的数组 [a] 的指针”类型。

            ((char (*) [a])0x0)[b]

拿那个指针,假设它指向一个数组,然后获取该数组的第 b 个元素。由于数组索引与指针算法相同,因此最终将计算 0 + a * b.

           &((char (*) [a])0x0)[b];

我们引用了“数组”的第 b 个元素。现在计算一个 那个元素的指针。该指针的字面值应为 0 + a * b.

(uintptr_t)&((char (*) [a])0x0)[b];

最后,获取该指针并将其转换为整数类型。


现在,综上所述,必须指出这是一个 hack。以这种方式编写代码来对空指针执行算术运算是非常有问题的。它可能几乎但不完全合法;它可能是合法的,但几乎不合法。您可能会为答案落在哪一边争论几个小时。

在这种情况下,当然,这是一种学术争论,因为没有人会认真地提议以这种方式进行乘法运算。

从技术上讲,这是未定义的行为。但是这段代码可能解决的预期功能假设一个朴素的编译器逻辑如下。

((char (*) [a])0x0) - 这需要一个地址 0x0 并将其转换为指向 a char 元素数组的指针,即指向对象的指针大小 a 字节。

现在,根据 C 指针算法,使用此指针的任何操作 (addition/subtraction) 都将以 a 的倍数执行。

接下来,它正在获取此指针的 b 偏移量。正如我们所知,p[b] 等价于任何指针 p*(p + b)。在我们的例子中 p 等于 0x0 并且是一个指向大小为 a 的对象的指针。因此 p + b 将具有 0x0 + b * sizeof(*p)0x0 + a * b 的数值。也就是 a * b.

此代码通过对无效指针执行指针运算来调用 undefined behavior。话虽如此,这就是它试图做的事情。

(char (*) [a])0x0 将值 0 转换为指向大小为 a 的数组的指针 char,为您提供指向占用 a 的对象的指针字节。

然后使用 &((char (*) [a])0x0)[b] 它使用数组索引获取此指针指向的 b 元素并获取其地址。

此外,因为 E1[E2] 类型的表达式与 *(E1 + E2) 完全相同,这意味着先前的表达式与 &(*((char (*) [a])0x0) + b) 相同,并且因为 &后跟[=​​20=]抵消这个和((char (*) [a])0x0) + b是一样的。所以没有对无效指针的取消引用。

因为指针算法将指针的值增加偏移量乘以元素大小,所以您现在有一个数值为 a*b 的指针。然后将该值转换为整数类型并返回。

未定义行为发挥作用的地方是数组索引中的隐式 + 运算符。仅当原始指针和加法结果都指向有效对象(或对象数组末尾后的一个元素)时,指针算法才有效。由于 0 不是有效地址,因此这是 UB。