高位保留是为了以后地址总线增加的情况下使用,因此您不能简单地使用它。
AMD64架构定义了一种64位虚拟地址格式,其中当前实现中使用低48位(...) 架构定义允许在未来实现中将此限制提高到完整的64位,将虚拟地址空间扩展到16 EB(2^64字节)。 这与x86的仅4 GB(2^32字节)相比。
http://en.wikipedia.org/wiki/X86-64#Architectural_features
更重要的是,根据同一篇文章 [强调我的]:
在架构的最初实现中,虚拟地址的最低有效48位将用于地址转换(页表查找)。此外,任何虚拟地址的第48到63位必须是第47位的副本(类似于符号扩展),否则处理器会引发异常。符合此规则的地址称为“规范形式”。
由于即使未使用,CPU也会检查高位,因此它们并不是真正的“无关紧要”。在使用指针之前,您需要确保地址是规范的。一些其他的64位架构,如ARM64,有忽略高位的选项,因此可以更轻松地在指针中存储数据。
话虽如此,在x86_64中,如果需要,仍然可以自由地使用高16位(如果虚拟地址不超过48位,请参见下文),但在解除引用之前,必须检查和修复指针值通过符号扩展它。
请注意,将指针值强制转换为long
并不是正确的方法,因为long
不能保证足够宽以存储指针。您需要使用uintptr_t
或intptr_t
。
int *p1 = &val;
uint8_t data = ...;
const uintptr_t MASK = ~(1ULL << 48);
int *p2 = (int *)(((uintptr_t)p1 & MASK) | (data << 56));
data = (uintptr_t)p2 >> 56;
intptr_t p3 = ((intptr_t)p2 << 16) >> 16;
val = *(int*)p3;
WebKit的JavaScriptCore和Mozilla的SpiderMonkey引擎以及LuaJIT在nan-boxing技术中使用了这个。如果该值为NaN,则低48位将存储对象指针,高16位用作标记位,否则它是双精度值。
以前Linux也使用GS基地址的第63rd位来指示该值是否由内核编写
实际上,您通常也可以使用第48th位。因为大多数现代64位操作系统将内核空间和用户空间平分,所以第47位始终为零,您有17个顶部位可供使用
你还可以使用
低位来存储数据,这被称为
tagged pointer。如果
int
是4字节对齐的,则2个低位始终为0,您可以像在32位架构中那样使用它们。对于64位值,您可以使用3个低位,因为它们已经对齐到8字节。同样,在引用之前,您也需要清除这些位。
int *p1 = &val;
int tag = 1;
const uintptr_t MASK = ~0x03ULL;
int *p2 = (int *)(((uintptr_t)p1 & MASK) | tag);
tag = (uintptr_t)p2 & 0x03;
intptr_t p3 = (uintptr_t)p2 & MASK;
val = *(int*)p3;
One famous user of this is the V8 engine with
SMI (small integer) optimization. The lowest bit in the address will serve as a tag for type:
- if it's 1, the value is a pointer to the real data (objects, floats or bigger integers). The next higher bit (w) indicates that the pointer is weak or strong. Just clear the tag bits and dereference it.
- if it's 0, it's a small integer. In 32-bit V8 or 64-bit V8 with pointer compression it's a 31-bit int, do a signed right shift by 1 to restore the value; in 64-bit V8 without pointer compression it's a 32-bit int in the upper half.
32-bit V8
|----- 32 bits -----|
Pointer: |_____address_____w1|
Smi: |___int31_value____0|
64-bit V8
|----- 32 bits -----|----- 32 bits -----|
Pointer: |________________address______________w1|
Smi: |____int32_value____|0000000000000000000|
https://v8.dev/blog/pointer-compression
所以根据下面的评论,英特尔已发布了
PML5,提供了
57位虚拟地址空间,如果你在这样的系统上,只能使用7个高位。
你仍然可以采用一些变通方法来获取更多的免费位数。首先,您可以尝试在64位操作系统中使用32位指针。在Linux中,如果允许x32abi,则指针只有32位长。在Windows中,只需清除
/LARGEADDRESSAWARE
标志,指针现在仅具有32个有效位,您可以使用上32位进行您的目的。请参阅
如何在Windows上检测X32?。另一种方法是使用一些
指针压缩技巧:
V8中压缩指针实现与JVM的压缩Oops有何不同?。
您可以通过请求操作系统仅在低地址区域分配内存来获得更多的位。例如,如果您可以确保您的应用程序永远不会使用超过64MB的内存,则只需要26位地址。如果所有的分配都是32字节对齐的,那么您可以再使用5个位,这意味着您可以在指针中存储64-21=43位信息!
我猜ZGC就是其中一个例子。它仅使用42位寻址,允许242字节= 4 × 240字节= 4 TB。
因此,ZGC仅保留了16TB的地址空间(但实际上并未使用所有这些内存),从4TB地址开始。
A first look into ZGC
它使用指针中的位如下:
6 4 4 4 4 4 0
3 7 6 5 2 1 0
+-------------------+-+----+-----------------------------------------------+
|00000000 00000000 0|0|1111|11 11111111 11111111 11111111 11111111 11111111|
+-------------------+-+----+-----------------------------------------------+
| | | |
| | | * 41-0 Object Offset (42-bits, 4TB address space)
| | |
| | * 45-42 Metadata Bits (4-bits) 0001 = Marked0
| | 0010 = Marked1
| | 0100 = Remapped
| | 1000 = Finalizable
| |
| * 46-46 Unused (1-bit, always zero)
|
* 63-47 Fixed (17-bits, always zero)
如需更多信息,请参见
小提示:对于与指针相比具有微小键值的情况使用链表会浪费大量内存,并且由于缓存局部性不佳而更慢。实际上,在大多数现实生活问题中都不应该使用链表。