如何通过置最后一位为1标记指针?位操作解引用遇段错误
位操作标记指针后解引用触发段错误的原因分析
为实现非阻塞链表,我尝试利用指针未使用的最后一位做标记——用OR操作置最后一位为1,AND操作复位该位,AND操作检查标记位。但使用位操作宏(如#define unmark(x) (x & (uintptr_t) 0xfffffffe))时,即便unmark后指针的整数值看起来正确,解引用仍会触发段错误;而用x-1的取消标记宏却能正常运行。已知加减1的方案存在架构相关性(当前架构指针末位始终为0),且指针操作本身可移植性较差,但想明确位操作导致段错误的具体原因。测试代码如下:
#include <stdio.h> #include <stdint.h> #include <stdlib.h> //This produces segfault, for some reason //#define unmark(x) (x & (uintptr_t) 0xfffffffe) //#define mark(x) (x | (uintptr_t) 0x00000001) #define is_marked(x) ((long) x & 0x00000001) #define mark(x) x + 1 #define unmark(x) x - 1 struct Example { long x; long y; }; int main() { struct Example *x = malloc(sizeof(struct Example)); x->x = 10; x->y = 20; uintptr_t p = (uintptr_t)(void*) x; printf("%ld\n", ((struct Example *) (void*) p)->y); printf("%04x\n", p); printf("Is marked: %d\n", is_marked(p)); p = mark(p); printf("%04x\n", p); printf("Is marked: %d\n", is_marked(p)); p = unmark(p); printf("%04x\n", p); printf("Is marked: %d\n", is_marked(p)); printf("%ld\n", ((struct Example *) (void*) p)->y); return 0; }
核心问题:固定长度掩码导致高地址位丢失
你遇到的段错误,根源出在位掩码的长度不匹配上:
- 你使用的
0xfffffffe是一个32位的十六进制常量,当强制转换为uintptr_t时,如果你的系统是64位架构(大部分现代系统都是),这个掩码只会覆盖低32位,高32位会被填充为0,变成0x00000000fffffffe。 - 执行
unmark(x)时,这个掩码会把指针的高32位全部清零,导致指针指向完全无效的内存地址,解引用自然触发段错误。 - 而加减1的操作是对整个
uintptr_t数值进行的,只会修改最后一位,不会破坏高地址位,因此能正确还原原指针。
额外问题:类型转换的潜在风险
你的is_marked宏使用(long)x进行转换,这在64位系统下可能隐含符号扩展问题:如果指针的最高位为1,转换为有符号的long会变成负数,但判断最后一位的逻辑暂时不受影响。更稳妥的做法是用uintptr_t来转换,避免符号干扰:
#define is_marked(x) ((uintptr_t)x & 1)
修复方案:使用与架构无关的掩码
要让位操作宏在32位和64位系统下都正常工作,需要生成与指针位数匹配的掩码,推荐两种写法:
- 取反单比特掩码:
~(uintptr_t)1,会生成所有位为1、仅最后一位为0的掩码,适配任何指针宽度。 - 利用补码特性:
(uintptr_t)-2,-2的无符号补码形式同样是仅最后一位为0的全1掩码。
修复后的位操作宏如下:
#define unmark(x) (x & ~(uintptr_t)1) #define mark(x) (x | (uintptr_t)1) #define is_marked(x) ((uintptr_t)x & 1)
替换后,unmark操作会正确保留所有高地址位,还原出原始指针,解引用就不会触发段错误了。
内容的提问来源于stack exchange,提问作者Andrejcc
相关产品推荐
相关产品推荐

