[英]How can I know how many free bits are there in a pointer?
我想我必须清楚我正在使用的平台以及我要求你更容易回答这个问题的原因:我正在使用带有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进行了编译,并在装有Ubuntu的64位计算机上运行了10000次迭代。 我发现&a
总是以最后4位为1100
而p总是以0000
结尾,因此对于保守的编译器实际未使用的位,我想保持保守。 这就是我说2而不是3的原因。
另外,如果您可以帮助我解释所观察到的结果( &a
最终为1100,只有2位设置为0),我将不胜感激。
许多建议不要在指针中使用标记位(我也是)。
如果您坚持要这样做,请仅使用最低的两个(或三个)指针,最好编写自己的malloc
的分配器以确保它。
在现代的x86-64处理器上,大多数指针(例如那些指向malloc
-ed区域或指向字对齐数据的指针)通常是字对齐的,这意味着它们是8的倍数(因为64位字有8个字节)。
实际上它不仅是处理器,还有ABI特定的。 一些ABI要求16字节对齐的堆栈指针(以帮助SSE或AVX ),其他ABI仅需要8字节对齐。
不要指望修复高位地址。 实际上它们通常是,但这是特定于处理器的(在高端Intel Xeon和低端FS1b AMD处理器上可能略有不同,并且在近期处理器中可能会有所不同)。
顺便说一句,这是OS(和处理器)特定的。 并考虑ASLR和VDSO 。
例如,在bigloo的源代码中, 查看文件runtime/Include/bigloo.h
作为标记的示例。
如果实现自己的解释器,相关的问题是垃圾收集器 。 我建议使用Boehm的保守GC ; 它可能不是最快或最好的,但它足够好(并且对线程友好)。 根据经验(例如在MELT中 ),调试GC非常耗时。
而且,今天,内存比标签计算重要得多。 注意CPU缓存 ,......
如果在Linux上,请查看/proc/self/maps
或/proc/$$/maps
(请参阅proc(5) )
您不能保证在每个64位机器上,编译器和C结构指针对齐到8个字节。 例如,在Intel i86上,硬件指针根本不需要对齐。
如果您正在处理与最近的字节对齐的压缩结构,例如
struct { char foo; char *p; } __attribute__((packed)) bar_t
然后指针不一定是对齐的(也可能不适用于基于字的架构)。
标记指针可能对于在一个人控制的固定平台上编写嵌入式软件的特殊情况很有用,但从文章中可以明显看出它不可移植。
当您不可避免地在几年后尝试在新平台上重用代码时,这将是一个等待发生的事故。
它实际上取决于您的平台(OS等)以及有关如何处理内存分配的任何可靠信息。
如果您的程序构建为x86(32位地址空间),并且您正在Windows操作系统上运行,并且没有启用Large Address Aware标志,那么您可以开始假设由于以下原因而未使用最高位:给x86程序分配内存空间的传统。
但是,我认为这与实施细节的伎俩比任何良好的做法更为重要。
毕竟,存在大地址感知标志的全部原因是因为一些先前的开发人员在Windows操作系统恰好将非低位操作系统代码的2GB地址空间给予时,会回放这些技巧。 因此,当提供超过2GB的技术进步是可行的时,微软不能只为所有程序打开它,因为没有好的方法可以知道哪些仍然有用,哪些用指针做了时髦的事情。
因此,他们必须发明“大地址感知”标志,以便开发人员有一种方法来指示其软件可以应对更高的地址值(大概是意识到这意味着他们不应出于其他目的而使用指针位来骗人)。
在我看来, 通常应该有一个更好的解决方案,无论你想用这些指针位做什么,但它真的取决于细节。
我也建议不要这样做。
但是,对于高位 ,有许多情况下它比OS更基础,因为某些处理器根本不使用这些位。
例如,amd64架构目前仅支持48位: https : //en.wikipedia.org/wiki/X86-64
摩托罗拉最初的68k只使用了32位中的24位 - 其他地址线都丢失了: https : //en.wikipedia.org/wiki/Motorola_68000#Address_bus
人们将其用于标记指针,并在添加后续处理器时遇到问题。
当前的x86-64实现支持48位虚拟地址。 它们要求虚拟地址位[63:48]
是位47的副本。否则地址是非规范的并且会[63:48]
。 (这个设计决策避免了在创建支持更多虚拟地址位的实现时的未来痛苦,因为代码不能假设硬件会忽略某些地址,而IIRC在以前的ISA中一直是个问题。)
有关图表,请参阅Wikipedia的x86-64文章中的规范地址部分 。
因此,您可以使用指针的高16位来存储其他内容,但是在使用之前,需要将其从48位符号扩展到64位以使其规范。 (例如,左移16,然后算术右移16)。
对于将在许多不同地方取消引用的指针,这是相当高的开销,因此使用单独的标记可能会更好。 如果cmpxchg16b相邻,则仍然可以对单独的指针和标记进行原子读写-修改-写入。 ( 编译器将使用std::atomic<two_member_struct>
上的compare_exchange_weak
为您执行此操作 。
如果只需要地址的低位用于对齐的指针,使用地址的低位可能会更便宜,因此您可以使用AND而不是两次移位来清除它们。 (64位AND掩码可以在机器代码中使用单字节sign-extended-imm8编码)。
如果您可以假设地址的高16位始终为零,而不是需要进行符号扩展,事情会变得容易一些。 然后你可以用常数将高位为零。 但是在asm中, 0x0000FFFFFFFFFFFF
不能用作AND指令的立即数。 它需要放在一个带有10字节movabs
(imm64)指令的寄存器中,或者用作内存操作数。
Linux通常使用规范范围较低的地址,但查看less /proc/self/maps
显示内核导出的[vsyscall]
页面位于上半部分。 malloc
/ mmap
不太可能返回高地址,但是我不希望在没有更多研究和控制使用这种假设的代码的条件的情况下依靠它来确保正确性。
在不久的将来,当x86-64实现支持超过48位的地址时,您的代码必须使用backwards-compat选项运行,该选项要求操作系统仅为您提供高位或低位47位的内存。 据推测,这样的选项将存在,因为可能已经有一些现有的代码对规范地址做出了假设。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.