C语言如何在指针中隐藏数据详解

2020-01-06 20:18:00丽君

前言

编写 C 语言代码时,指针无处不在。我们可以稍微额外利用指针,在它们内部暗中存储一些额外信息。为实现这一技巧,我们利用了数据在内存中的自然对齐特性。

内存中的数据并非保存在任意地址。处理器通常按照其字大小相同的块读取内存数据;那么考虑到效率因素,编译器会按照块大小的整数倍对内存中的实体进行地址对齐。因此在 32 位的处理器上,一个 4 字节整型数据肯定存放在内存地址能被4整除的地方。

下面,假设系统中整型数据和指针大小均为 4 字节。

现在有一个指向整型的指针。如上所述,整型数据可以存放在内存地址 0x1000 或者 0x1004 或者 0x1008,但是决不会存放在 0x1001 或者0x1002 或者 0x1003 或者其他不能被4整除的任何地址。所有是4整数倍的二进制数都是以 00 结尾。实际上,这意味着对于所有指向整型的指针,它的最后两位总是 0。

那么有 2 比特没有承载任何信息。此处的技巧是将我们的数据放置到这两个比特中,在需要时使用,并在通过指针解引用来访问内存前删除它们。

由于 C 标准对指针位操作的支持不是很好,所以我们将指针保存为一个无符号整型数据。

下面是一段简短的简单代码片段。完整的代码查看 github 代码仓库中的hide-data-in-ptr。


void put_data(int *p, unsigned int data)
{
	assert(data < 4);
	*p |= data;
}
unsigned int get_data(unsigned int p)
{
	return (p & 3);
}
void cleanse_pointer(int *p)
{
	*p &= ~3;
}
int main(void)
{
	unsigned int x = 701;
	unsigned int p = (unsigned int) &x;
	printf("Original ptr: %un", p);
	put_data(&p, 3);
	printf("ptr with data: %un", p);
	printf("data stored in ptr: %un", get_data(p));
	cleanse_pointer(&p);
	printf("Cleansed ptr: %un", p);
	printf("Dereferencing cleansed ptr: %un", *(int*)p);
	return 0;
}