我想我必须清楚我正在使用的平台以及我要求你更容易回答这个问题的原因:我正在使用带有gcc和Ubuntu的x86_64机器,我正在研究一些解释器一些玩具语言,我认为标记指针是一个巧妙的技巧可以使用。我知道Apple正在使用它。所以我只想尝试一下。
我正在阅读有关标记指针的内容,我想知道,我怎么知道特定机器上指针中有多少空闲位。
现在我的理解是,如果我使用64位机器,那么当访问内存时,CPU将始终访问8字节倍数的内存地址。所以它在指针的末尾留下2位总是设置为0.另外,如果在x86_64机器上,前14位总是0对吗?因为它们从未被CPU使用过。 malloc
将确保它返回的指针始终对齐。但是其他内存位置呢?说堆栈上的变量?
我该如何确认?
评论中有人建议我在上面提到的2位不对,表明我是一个糟糕的程序员。我不否认我不是一个非常专业的程序员,但我想我会解释一下为什么我说2而不是3。
我写了一个非常简单的程序:
#include <stdio.h>
#include <stdlib.h>
int main() {
int a = 0;
printf("%p\n", &a);
int *p = malloc(sizeof(int));
printf("%p\n", p);
}
我用gcc编译它并在64位机器上用Ubuntu运行10000次迭代。我发现&a
总是最后4位为1100
而p总是以0000
结束,所以我想保守编译器实际使用了多少位。这就是为什么我说2而不是3。
另外,如果你可以帮我解释一下我观察到的内容(&a
以1100结尾,只有2位设置为0),我会非常感激。
答案 0 :(得分:3)
许多人建议不要在指针中使用标记位(我也是如此)。
如果你坚持这样做,只使用指针的两个(或者三个)最低位,最好编写你自己的malloc
- 像分配器一样来确保它。
在现代x86-64处理器上,大多数指针(例如malloc
- ed区域或指向字对齐数据的指针)通常是字对齐的,这意味着它们是8的倍数(因为64)位字有8个字节)。
实际上它不仅是处理器,还有ABI特定的。一些ABI要求一个16字节对齐的堆栈指针(以帮助SSE或AVX),其他ABI只需要8字节对齐。
不要指望修复高位地址。实际上它们通常是,但这是特定于处理器的(在高端Intel Xeon和低端FS1b AMD处理器上可能略有不同,并且在不久的将来处理器中可能会有所不同)。
BTW,这是OS(和处理器)特有的。并采取ASLR&amp;考虑VDSO。看看,例如在bigloo的源代码中,文件runtime/Include/bigloo.h
作为标记示例。
如果实施自己的口译员,则相关问题是garbage collector。我建议使用Boehm's conservative GC;它可能不是最快或最好的,但它足够好(并且线程友好)。根据经验(例如在MELT中),调试GC非常耗时。
此外,今天,内存比标签计算重要得多。请注意CPU cache,...
如果在Linux上,请查看/proc/self/maps
或/proc/$$/maps
(请参阅proc(5))
答案 1 :(得分:2)
您不能保证在每个64位机器上,编译器和C结构指针对齐到8个字节。例如,在Intel i86上,硬件指针根本不需要对齐。
如果您正在处理与最近的字节对齐的压缩结构,例如
struct { char foo; char *p; } __attribute__((packed)) bar_t
然后指针不一定会对齐(也可能不适用于基于字的体系结构)。
Tagged Pointers可能对于在一个人控制的固定平台上编写嵌入式软件的特殊情况很有用,但从文章中可以明显看出它不可移植。
当你不可避免地试图在几年后在新平台上重复使用代码时,这将是一个偶然的事。
答案 2 :(得分:2)
这实际上取决于您的平台(操作系统等)以及有关如何处理内存分配的任何可靠信息。
如果您的程序是构建为x86(32位地址空间)并且您在Windows操作系统上运行并且您没有打开大地址识别标志,那么您可以开始做出关于由于x86程序如何获得内存空间的遗留问题,最高位未被使用。
然而,我认为这与实施细节的伎俩比任何良好做法更为重要。
毕竟,大地址识别标志存在的全部原因是因为一些以前的开发人员在Windows操作系统恰好给非OS代码提供较低2GB的地址空间时会回放这些技巧。因此,当技术先进,提供超过2GB的可行性时,微软无法为所有程序打开它,因为没有好的方法可以知道哪些仍然有用,哪些是用指针做些时髦的东西。
因此,他们不得不发明大地址识别标志,以便开发人员有办法表明他们的软件可以处理更高的地址值(大概是意识到这意味着他们不应该使用指针位来玩弄技巧其他目的)。
在我看来,通常应该是一个更好的解决方案,无论你想用这些指针位做什么,但它实际上取决于细节。
答案 3 :(得分:0)
我也建议不要这样做。
然而,对于高位,有很多情况下它比OS更基础,因为有些处理器根本不使用这些位。
E.g。 amd64架构目前只支持48位: https://en.wikipedia.org/wiki/X86-64
摩托罗拉最初的68k只使用了32位中的24位 - 其他地址线根本就丢失了: https://en.wikipedia.org/wiki/Motorola_68000#Address_bus
人们将其用于标记指针,并在添加后续处理器时遇到问题。
答案 4 :(得分:0)
当前的x86-64实现支持48位虚拟地址。它们要求虚拟地址位[63:48]
是位47的副本。否则该地址是非规范的并且将是错误的。 (这个设计决策避免了在创建支持更多虚拟地址位的实现时的未来痛苦,因为代码不能假设硬件将忽略某些地址,这在以前的ISA中一直是IIRC的问题。)
另见the canonical address section in Wikipedia's x86-64 article图表。
因此,您可以使用指针的高16位来存储其他内容,但在使用之前,您需要将其从48位扩展到64位以使其成为规范。 (例如,左移16,然后算术右移16)。
对于将在许多不同位置解除引用的指针,这是非常高的开销,因此单独的标记可能更好。如果cmpxchg16b相邻,则仍然可以对单独的指针和标记进行原子读取 - 修改 - 写入。 (Compilers will do this for you with compare_exchange_weak
on std::atomic<two_member_struct>
)。
如果只需要它来处理对齐的指针,那么使用地址的低位可能会更便宜,所以你可以用AND而不是两个移位来清除它们。 (64位AND掩码可以在机器代码中使用单字节sign-extended-imm8编码)。
如果您可以假设地址的高16位始终为零,而不是需要进行符号扩展,事情会变得容易一些。然后你可以用常数将高位为零。但是在asm中,0x0000FFFFFFFFFFFF
并不是AND instruction的直接常量。它需要放在一个带有10字节movabs
(imm64)指令的寄存器中,或者用作内存操作数。
Linux通常使用较低规范范围内的地址,但查看less /proc/self/maps
表示内核导出的[vsyscall]
页面位于上半部分。 malloc
/ mmap
可能不会返回高地址,但我不想在没有更多研究的情况下依赖它来获得正确性,并控制条件使用了这个假设的代码。
在不久的将来,当x86-64实现支持超过48位的地址时,您的代码必须使用backwards-compat选项运行,该选项要求操作系统仅为您提供高位或低位47位的内存。据推测,这样的选项将存在,因为可能已经有一些现有的代码可以对规范地址做出假设。