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

 更新时间:2018年12月30日 11:55:15   作者:忑忑忑易昂  
这篇文章主要给大家介绍了关于C语言如何在指针中隐藏数据的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧

前言

编写 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;
}

代码输出如下:

Original ptr:  3216722220
ptr with data: 3216722223
data stored in ptr: 3
Cleansed ptr:  3216722220
Dereferencing cleansed ptr: 701

我们可以在指针中存储任何可以用两个比特位表示的数据。使用 put_data() 函数,设置指针的最低两位为要存储的数据。该数据可以使用get_data() 函数获取。此处除了最后两位所有的位都被覆盖为零,于是我们隐藏的数据就显示出来。

cleanse_pointer() 函数将最低两位置零,保证指针安全地解引用。注意虽然有些 CPU(像 Intel 允许我们访问未对齐内存地址,但其余 CPU(像 ARM)会出现访问错误。所以,要牢记在解引用前保证指针指向已对齐内存地址。

这在实际中有应用吗?

是的,有应用。查看 Linux 内核中红黑树的实现(链接:https://github.com/torvalds/linux/blob/master/include/linux/rbtree.h)。

树的结点定义如下:

struct rb_node {
	unsigned long  __rb_parent_color;
	struct rb_node *rb_right;
	struct rb_node *rb_left;
} __attribute__((aligned(sizeof(long))));

此处 unsigned long __rb_parent_color 存储了如下信息:

父节点的地址

结点的颜色

色彩的表示用 0 代表红色,1 代表黑色。

和前面的例子一样,该数据隐藏在父指针“无用的”比特位中。

下面看一下父指针和色彩信息是如何获取的:

/* in rbtree.h */
#define rb_parent(r) ((struct rb_node *)((r)->__rb_parent_color & ~3))
/* in rbtree_augmented.h */
#define __rb_color(pc)  ((pc) & 1)
#define rb_color(rb)  __rb_color((rb)->__rb_parent_color)

内存中每一比特都很珍贵,咱们永远不要浪费。——(本文作者)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • 使用C语言实现字符串逆序操作案例

    使用C语言实现字符串逆序操作案例

    这篇文章主要介绍了使用C语言实现字符串逆序操作案例,本文包含使用C语言的两种方法去实现,递归和非递归,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • c++中的string常用函数用法总结

    c++中的string常用函数用法总结

    以下是对c++中的string常用函数的用法进行了详细的分析介绍,需要的朋友可以过来参考下
    2013-09-09
  • typedef_struct与struct之间的区别

    typedef_struct与struct之间的区别

    本篇文章主要是对typedef struct与struct之间的区别进行了介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2013-12-12
  • 二分图匹配实例代码及整理

    二分图匹配实例代码及整理

    这篇文章主要介绍了二分图匹配实例代码及整理的相关资料,这里提供了三种方法包括匈牙利算法,KM算法,多重匹配,需要的朋友可以参考下
    2017-07-07
  • C/C++中宏定义(#define)

    C/C++中宏定义(#define)

    #define命令是C语言中的一个宏定义命令,它用来将一个标识符定义为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。接下拉通过本文给大家分享C/C++中宏定义(#define)知识,需要的朋友参考下
    2017-02-02
  • C/C++中获取数组长度的方法示例

    C/C++中获取数组长度的方法示例

    这篇文章主要介绍了C/C++中获取数组长度的方法,很实用的一种方法,需要的朋友可以参考下
    2014-08-08
  • linux下使用g++编译cpp工程的方法

    linux下使用g++编译cpp工程的方法

    这篇文章主要介绍了linux下使用g++编译cpp工程的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-03-03
  • c语言实现http下载器的方法

    c语言实现http下载器的方法

    这篇文章主要介绍了c语言实现http下载器的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-07-07
  • C语言解数独程序的源码

    C语言解数独程序的源码

    这篇文章主要为大家详细介绍了C语言解数独程序的源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-01-01
  • C++中的聚合类定义与用法分析

    C++中的聚合类定义与用法分析

    这篇文章主要介绍了C++中的聚合类定义与用法,结合实例形式分析了C++中聚合类的简单定义、使用方法与相关注意事项,需要的朋友可以参考下
    2017-08-08

最新评论