在编写C代码的过程中指针是一个频繁出现且极为重要的元素可以说是无处不在。实际上我们还能对指针进行一些巧妙的额外运用比如在指针里偷偷存储一些额外的信息。而实现这一巧妙技巧的关键就在于巧妙利用内存中数据的自然对齐特性。内存里的数据存储并非随意地安排在任意的地址上。处理器在读取内存时总是按照与自身字长相同大小的块来进行读取。从提高效率的角度出发编译器会将内存中的各种实体如变量等的地址分配为它们自身大小以字节为单位的整数倍。举个例子在32位处理器的环境下一个占据4字节空间的整数类型数据必然会被存储在能被4整除的内存地址之上。在这里我们先设定一个前提条件假设在某个系统中int类型整数类型所占用的空间大小和指针类型所占用的空间大小都是4字节。接下来让我们来深入思考一个指向int类型数据的指针。就像前面所提到的那样int类型的数据有可能被存储在像0x1000、0x1004或者0x1008这样的内存地址位置但绝对不会被存储在0x1001、0x1002、0x1003或者其他任何不能被4整除的内存地址上。我们知道在二进制表示中任何一个能够被4整除的二进制数其末尾的两位数字必然都是00。这也就意味着对于任意一个指向int类型数据的指针而言它所对应的内存地址值的二进制表示中最右边的两个低阶位始终是零。现在我们发现了指针的这两个低阶位在正常情况下并没有实际的用途相当于是“闲置”的。这样这里的技巧就在于我们可以把想要存储的额外数据巧妙地放置到这两个低阶位中。在后续需要使用这些数据的时候我们可以将其提取出来使用而在通过解引用指针去访问内存中的实际数据之前我们需要把存储在这两个低阶位中的数据移除掉以确保指针的正常使用。由于在C语言的标准规范中直接对指针进行按位操作是不被允许的不太符合标准要求。所以为了实现我们的目的我们会把指针转换为unsigned int无符号整数类型来进行存储和相关操作。为了让大家能快速理解核心思路下面先展示一段较为简单的代码片段。我们把这段代码执行完后就会得出下面这样的输出结果从这里我们能够晓得我们可以于指针当中进行存储而且实际上我们能够存储任何能够通过2位二进制数予以表示的数字。当使用putdata()函数时指针所对应的内存地址值的二进制表示中的最后两位会被设置为我们想要存储的数据。而通过getdata()函数我们就可以访问到存储在指针这两个低阶位中的数据。具体来说getdata()函数会将除了最后两位之外的所有位都覆盖为零这样就能把我们之前隐藏存储的数据显示出来了。cleansepointer()函数的作用则是将指针所对应的内存地址值的二进制表示中的最后两位清零。这样做的目的在于在对指针进行解引用操作之时而且当指针所指向的内存地址满足那种特定的对齐要求之时进而能够保障解引用操作的安全性。需要特别注意的是虽然诸如英特尔Intel所生产的部分CPU在某些情形下并且在某些特定的环境里能够准许程序去访问未对齐的内存位置不过像ARM CPU等其他一些类型的CPU如果去访问未对齐的内存位置那这样一来反倒会致使程序出错。所以在对指针进行解引用操作之前一定要牢记让指针指向一个满足对齐要求的内存位置。这种方法在现实世界中有应用吗答案是肯定的这种在指针中隐藏数据的方法在实际应用中是有其用武之地的。我们可以查看Linux内核中红黑树Red Black Trees的实现。在Linux内核里红黑树的节点是按照如下这般方式来予以定义的首先存有特定的数据结构以及与之相关联的属性其次凭借一系列的规则与操作来维系其特性并且于整个内核的运行进程当中它发挥着极为重要的作用。在这里unsigned long rbparentcolor这个变量存储了两个重要的信息1. 红黑树中当前节点的父节点的地址。2. 当前节点的颜色信息。其中颜色用0来表示红色用1来表示黑色。就如同我们前面所举的例子一样这些信息节点颜色信息被巧妙地偷偷存储在了表示父节点地址的指针的“无用”位中。现在让我们来看看在Linux内核的相关代码中是怎样去访问父节点指针以及节点颜色信息的参考文献《Hide data inside pointers》