不使用 * operator hack-y 方式获取产品
Get Product without using * operator hack-y way
有人可以帮我理解以下逻辑如何解决获取 a
和 b
的乘积吗?
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。
有人可以帮我理解以下逻辑如何解决获取 a
和 b
的乘积吗?
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。