-->

使用64位指针额外的16位(Using the extra 16 bits in 64-bit po

2019-09-01 02:21发布

我读64位机实际上使用地址的唯一的48位 (具体地,我使用英特尔核i7)。

我预计,额外的16位(48-63)无关的地址,将被忽略。 但是,当我尝试访问这样的地址我得到了一个信号, EXC_BAD_ACCESS

我的代码是:

int *p1 = &val;
int *p2 = (int *)((long)p1 | 1ll<<48);//set bit 48, which should be irrelevant
int v = *p2; //Here I receive a signal EXC_BAD_ACCESS.

为什么会这样? 有没有使用这些16位的方法吗?

这可以用来建造更多的缓存友好链接列表。 代替使用8个字节为下PTR,和8个字节为键(由于对齐限制)的,关键可以嵌入到指针。

Answer 1:

高序位的情况下,保留的地址总线将在未来增加,所以你不能简单地使用它像

AMD64架构定义了一个64位虚拟地址格式,这低阶的48位在当前的实现(...)的架构定义允许此限制在将来的实现提高到全64位 ,延长了使用虚拟地址空间到16 EB(2个64字节)。 这相对于仅有4 GB(2个32字节)用于x86。

http://en.wikipedia.org/wiki/X86-64#Architectural_features

更重要的是,根据同一篇文章[重点煤矿]:

......在建筑的第一实现,只有最显著48虚拟地址的位究竟会在地址转换(页表查找)使用。 此外, 位48通过任何虚拟地址63必须是47位 (在类似于符号扩展的方式) 的副本 ,或者,处理器将产生一个异常。 这个规则遵守地址被称为“规范形式。”

由于CPU将检查高位即使他们未使用的,他们并不是真正的“无关”。 您需要确保该地址是使用指针之前规范。 像ARM64其他一些64位架构有忽略高位的选项,因此您可以更轻松地存储在指针数据。


这就是说,在x86_64的你还是免费的,如果需要使用高16位,但你必须检查并提领之前符号扩展固定指针值。

需要注意的是指针值强制转换为long 不这样做 ,因为正确的方法 long不能保证足够宽存储指针。 您需要使用uintptr_tintptr_t

int *p1 = &val; // original pointer
uint8_t data = ...;
const uintptr_t MASK = ~(1ULL << 48);

// store data into the pointer
//     note: to be on the safe side and future-proof (because future implementations could
//     increase the number of significant bits in the pointer), we should store values
//     from the most significant bits down to the lower ones
int *p2 = (int *)(((uintptr_t)p1 & MASK) | (data << 56));

// get the data stored in the pointer
data = (uintptr_t)p2 >> 56;

// deference the pointer
//     technically implementation defined. You may want a more
//     standard-compliant way to sign-extend the value
intptr_t p3 = ((intptr_t)p2 << 16) >> 16; // sign extend the pointer to make it canonical
val = *(int*)p3;

WebKit的JavaScriptCore的,并在Mozilla的SpiderMonkey的引擎使用本南拳击技术 。 如果该值是NaN,则低48位将所述指向对象存储与高16位用作标签位,否则这是一个双精度值。


您还可以使用较低位来存储数据。 这就是所谓的一个标记指针 。 如果int然后对齐4字节的2的低位总是0,并且可以使用它们像32位体系结构。 对于64位的值,因为他们已经8字节对齐你可以使用3个低位。 同样,你也需要解引用之前清除这些位。

int *p1 = &val; // the pointer we want to store the value into
int tag = 1;
const uintptr_t MASK = ~0x03ULL;

// store the tag
int *p2 = (int *)(((uintptr_t)p1 & MASK) | tag);

// get the tag
tag = (uintptr_t)p2 & 0x03;

// get the referenced data
intptr_t p3 = (uintptr_t)p2 & MASK; // clear the 2 tag bits before using the pointer
val = *(int*)p3;

这方面的一个著名的用户是32位版本的V8与SMI(小的整数),优化 (我不知道64位V8虽然)。 最低位将作为类型标签: 如果是0,这是一个小的31位整数,做由1签署右移恢复值; 如果是1,该值是一个指向真实数据(对象,漂浮或更大的整数),只需清除标记,并取消对它的引用

附注:使用链表相比于指针微小的关键值的情况下是一个巨大的浪费内存,而且它也比较慢,由于恶劣的缓存位置。 事实上,你不应该使用链接的最真实生活中的问题列表

  • Bjarne的Stroustrup的说,我们必须避免链表
  • 为什么你应该永远,永远,永远在你的代码中使用链表再次
  • 数字运算:为什么你应该永远,永远,永远再在代码中使用链表
  • Bjarne的Stroustrup的:为什么你应该避免链表
  • 是列表邪?-Bjarne斯特劳斯


Answer 2:

根据英特尔手册(第1卷,节3.3.7.1)线性地址必须是在规范形式。 这意味着,仅确实48位被用于和额外的16位被位扩展。 此外,实施需要检查的地址是否以这样的形式,如果它不产生异常。 这就是为什么没有办法使用这些额外的16位。

为什么在这样的方式做的原因很简单。 目前,48位虚拟地址空间是绰绰有余的(并且由于CPU生产成本存在使其成为更大的没有意义),但无疑在未来将需要额外的比特。 如果应用程序/内核是将其用于自己的目的兼容性问题会出现,这就是CPU厂商希望避免的。



Answer 3:

物理内存是48位寻址。 这足以解决大量的RAM。 但是你的程序之间运行的CPU核心和内存是内存管理单元,CPU的一部分。 你的程序是解决虚拟内存和MMU负责虚拟地址和物理地址之间的转换。 虚拟地址是64位。

虚拟地址的值不能告诉你相应的物理地址。 事实上,由于虚拟内存系统是如何工作没有保证相应的物理地址是相同的每时每刻。 如果你发挥创意与mmap()的,你可以让两个或多个虚拟地址指向同一物理地址(无论这种情况是)。 然后,如果您写信给任何你实际写入只是一个物理地址的虚拟地址(无论这种情况是)。 这种伎俩在信号处理非常有用。

因此,当你与你的指针的第48位(这是在一个虚拟地址指向)的MMU无法找到由OS中分配给您的程序存储器表新地址篡改(使用malloc或自己()) 。 它提出了抗议中断时,OS捕捉任何与你所提到的信号终止程序。

如果你想知道更多,我建议你谷歌“现代计算机体系结构”,并做一些阅读有关巩固你的程序的硬件。



文章来源: Using the extra 16 bits in 64-bit pointers