C语言指针的另类用法,眼花缭乱地类型变化,指针地址中还可以存入额外数据,到底有多神通呢?

指针的奇特用法

专栏内容

开源贡献

个人主页我的主页
管理社区开源数据库
座右铭:天行健,君子以自强不息;地势坤,君子以厚德载物.

概述

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_in 是socket编程中要用到的参数类型,它的定义如下
struct sockaddr 
{
    
    
    unsigned short sa_family;  /* 地址族, AF_xxx */
    char sa_data[14];  /* 14字节的协议地址*/
};
  • struct sockaddr_in 是在internet 中的网络地址
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 <stdio.h>

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]
如有错误或者疏漏欢迎指出,互相学习。

注:未经同意,不得转载!

猜你喜欢

转载自blog.csdn.net/senllang/article/details/132443073