专栏内容:
- postgresql内核源码分析
- 手写数据库toadb
- 并发编程
开源贡献:
- toadb开源库
个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.
C语言的指针想必大家既喜欢又恨的牙痒痒吧,喜爱的是它的灵活,便捷,无孔不入;恨它的是,不易控制,杀敌八百自损一千,还不如少用;
但它又是逃不过去的,那么我们只有摸清它的套路,才能应对自如,下面我们就来一起看看它的一些奇特用法,熟习之后,你也可以用的得心应用。
指针数据常常被用来在各种类型间互相转换,最常见的就是 malloc,将char * 强转成需要的类型。 除此之外,它还有一些常用的套路。
IPC或者RPC通信中,消息类型五花八门,消息体也是各有千秋,那么如何用统一的接收发送模块来处理呢?
定义下面是结构体
/* 公共消息类型定义 */
typedef struct MessageHeaderData
{
int messageType;
int mSize;
int token;
}MessageHeaderData, *PMessageHeader;
/* 业务消息定义,各自业务模块分别定义 */
typedef struct MessageData
{
MessageHeaderData mheader;
int hostid;
char data[];
}HostInfoData, *PHostInfo;
/* 还有很多其它消息类型的定义 */
对于这样的定义,就可以使用统一的模块来处理,在处理模块中,使用 PMessageHeader 类型来处理;此处并不关心消息体的内容,只处理header中的信息即可,根据类型进行投递;
当真正的消息处理模块收到时,它是知道自己要处理的类型的, 再转换成PHostInfo类型;
这样我们就可以使用统一的收发,消息队列,网络粘包处理程序,这些程序中也不需要关心业务的信息。
还有在链表使用时,也可以用类似的方法,我们不需要为每种数据结构都定义链表类型,而是使用统一的头部链表结构,这样就可以用一套链表处理方法来处理。
当程序代码越来越复杂,调用层次也会随之变得越来越深,参数传递所有信息就显得耦合度太高了,理应只传递当前模块划分层次的数据,
但是又能上下层次或在公共模块时,能通过其中之一找到其它的数据呢?
下面来看如何处理,首先定义如下结构
#define NAME_LEN 16
typedef struct Lock
{
int lockid;
int locktag;
}Lock, *PLock;
typedef struct RuntimeDescData
{
int id;
char name[NAME_LEN];
/* other members */
PLock lock;
/* other members */
}RuntimeDescData, *PRuntimeDesc;
当我们对某个RuntimeDescData资源加锁后,假如在加锁期间发生的错误,需要释放锁以及使用过的RuntimeDescData资源;
那么在加锁时,我们只需要加锁时,将struct Lock 记入一个列表中,在异常时,通过lock成员找到RuntimeDescData资源,然后进行释放;
下面我们来看具体的操作代码
/* 关键就是这个宏 */
#define GetRuntimeDescData(lock) ((PRuntimeDesc)(((char *)lock) - (unsigned int)(((PRuntimeDesc)(0))->lock)))
/* 释放处理代码 */
PLock lock[]; /* 记录了待释放资源 */
PRuntimeDesc runtimeDesc = NULL;
runtimeDesc = GetRuntimeDescData(lock[i])
ReleaseResource(runtimeDesc);
其中的关键代码就是 GetRuntimeDescData 宏定义, 因为RuntimeDescData中,各个成员都是连续存储的,其实就是将lock指针转正字符指针,然后向后移动 id + name 两个字段的偏移size;
那么这里有一个巧妙的用法,如可找到RuntimeDescData结构体中 lock成员偏移大小呢, 假设RuntimeDescData结构体的起始地址就是 0, 那么lock成员的地址 也就是它较地址0的偏移了。
当然也可以用其它方法获取偏移,如
/* 方法一 根据成员地址 - 结构体首地址 */
#define GetRuntimeDescData(runtimeDesc) ((unsigned long)(&(runtimeDesc->lock)) - (unsigned long)(&(type->id)));
/* 方法二 使用C标准库提供的宏 */
int offset = offsetof(struct RuntimeDescData, lock);
有一些结构体,它在不同场合下存储不同的数据,虽然我们可以将它们定义成不同的数据体构,但是会产生一些错觉,误会,以为是不同的存储位置。
比如说,洗衣机在大多数场合下,就是放衣服来洗;但在一些场合下,还可以放土豆来洗;它们都是洗衣机没有变,如果用不同的机器,那就没什么稀奇了。
比如我们网络编程常常用到的 struct sockaddr_in, struct sockaddr结构体,
struct sockaddr
{
unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14字节的协议地址*/
};
struct sockaddr_in
{
short int sin_family; /* 地址族 */
unsigned short int sin_port; /* 端口号 */
struct in_addr sin_addr; /* Internet地址 */
unsigned char sin_zero[8]; /* 与struct sockaddr一样的长度 */
};
这两个通常是可以互相转换的, 一般定义时使用 sockaddr_in结构,而传入socket接口时,将它转换为sockaddr 指针类型。
指针地址是字节对齐的,也就是说某个结构体大小是4字节的倍数,那么它的指针地址低两位是0,有些同学就在想,这些字节太浪费了,那如何利用呢?
下面我们先来看一下这一现象,定义几个结构体
#include
typedef struct message
{
int mtype;
}message;
typedef struct globalId
{
unsigned long id;
}gid;
typedef struct timeSerial
{
unsigned long th;
unsigned long tl;
}timeSerial;
int main()
{
message ms;
message *p = &ms;
gid id;
gid *g = &id;
timeSerial ts;
timeSerial *pt = &ts;
printf("message size %d,address %p \n", sizeof(message),p);
printf("gid size %d,address %p \n", sizeof(gid),g);
printf("timeSerial size %d,address %p \n", sizeof(timeSerial),pt);
return 0;
}
[senllang@hatch bin]$ gcc test.c
[senllang@hatch bin]$ ./a.out
message size 4,address 0x7ffcbb20c4c4
gid size 8,address 0x7ffcbb20c4b8
timeSerial size 16,address 0x7ffcbb20c4a0
可以看到结构体大小是4字节时,指针低两位是0,结构体大小是8时,低三位为0,而当结构体大小为16时,低四位为0; 这一特性在x86架构上是成立的;
那么我们就可以通过位运算存入几个bit的数据,在使用指针时,需要将低位置0即可。
#define HIGH_FLAG (1)
#define LOW_FLAG (2)
#define FLAG_MASK (3)
/* 存入数据 */
p = (message *)((unsigned long)p | HIGH_FLAG);
printf("message size %d,address %p \n", sizeof(message),p);
/* 使用指针时,屏蔽数据 */
p = (message *)((unsigned long)p &( ~FLAG_MASK));
printf("message size1 %d,address %p \n", sizeof(message),p);
非常感谢大家的支持,在浏览的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加努力!
作者邮箱:[email protected]
如有错误或者疏漏欢迎指出,互相学习。
注:未经同意,不得转载!