【问题标题】:Subtracting NULL pointer from a normal pointer generates arithmetic right shift从普通指针中减去 NULL 指针会生成算术右移
【发布时间】:2019-08-30 21:10:54
【问题描述】:

这是 C 代码。

int main() {
  int i = 10, *p = &i;
  printf("%ld", p - (int *) NULL);
}

对于指针算术部分,'gcc' 和 'clang' 在它们的汇编输出中生成一个 'sar rax, 2' 指令。有人可以解释一下这种情况下的指针算术与算术右移有何关系。

【问题讨论】:

  • 您的代码调用了 UB。 2个指针的区别must be printed with %zd
  • @phuclv: 不完全是:ptrdiff_tprintf 格式为%tdsize_tptrdiff_t 通常大小相同且符号不同,但C 不保证标准。将差异转换为 (long)(p - (int *)NULL) 会产生已定义的代码,但可能会产生与 printf("%lld", (long long)(p - (int *)NULL)); 不同的结果,例如在 64 位 Windows 上。
  • 请注意,两个不引用同一数组(或该数组末尾的指针)的指针相减也是未定义的行为。每6.5.6 Additive operators, paragraph 9 of the C standard:“当减去两个指针时,都应指向同一个数组对象的元素,或者指向数组对象的最后一个元素之后的元素。...”

标签: c pointers null-pointer


【解决方案1】:

右移 2 是一种快速除以 4 的方法。4 是您的 int 大小。

两个指针到int 的距离是两个char 指针对应int 指针的距离除以int 大小(请记住,将整数添加到指针时,整数被缩放指针目标大小,所以当你做差异时,你需要撤消这个缩放)。

从技术上讲,您不应该减去两个不相关的指针(或使用 "%ld" 而不是正确的 "%zd" 打印差异),因为这是未定义的行为 - 该标准只允许您区分指向同一对象的指针或刚刚过去。然而,一个通用的int*-diffing 函数本身没有这种未定义的行为:

#include <stddef.h>

ptrdiff_t diff_int_ptr(int *A, int *B)
{
    return A-B;
}

仍将转换为等价于

ptrdiff_t diff_int_ptr(int *A, int *B)
{
    return ((char*)A - (char*)B) >> lg2(sizeof(int));
    //strength reduced from: return ((char*)A - (char*)B) / sizeof(int)
    //which is possible iff sizeof(int) is a power of 2
}

在优化编译器 (godbold link for x86-64 gcc and clang) 上,因为移位通常比现代机器上的除法快 10 倍以上。

【讨论】:

  • 感谢您的解释以及网络链接。
猜你喜欢
  • 1970-01-01
  • 2016-04-25
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 2019-09-08
  • 1970-01-01
  • 2021-04-19
相关资源
最近更新 更多